🔬 단순 카운트 테스트 (std::atomic vs mutex)#
std::atomic#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| #include<iostream>
#include<iomanip>
#include<atomic>
#include<thread>
#include<mutex>
#include<chrono>
#include<vector>
const size_t THREAD_NUM = 12;
const size_t COUNT_NUM = 50'000'000;
std::atomic<int> atomic_counter(0);
void increse_counter_with_atomic(std::memory_order mo) {
for(size_t i = 0; i < COUNT_NUM; i++) {
atomic_counter.fetch_add(1, mo);
}
}
int main() {
std::vector<std::thread> threads;
std::chrono::steady_clock::time_point start;
std::chrono::steady_clock::time_point end;
double ms = 0.0;
// 스레드 생성 및 join
start = std::chrono::steady_clock::now();
for(size_t i = 0; i < THREAD_NUM; i++) {
threads.emplace_back(increse_counter_with_atomic, std::memory_order_seq_cst);
}
for(std::thread &th : threads) {
if(th.joinable()) {
th.join();
}
}
end = std::chrono::steady_clock::now();
ms = (std::chrono::duration_cast<std::chrono::milliseconds>(end - start)).count();
// 결과 출력
std::cout << std::fixed << std::setprecision(3); // 소수점 아래 3자리 반올림
std::cout << "duration: " << ms << "ms\n";
std::cout << "count: " << atomic_counter.load() << "\n";
return 0;
}
|
No. | duration | result |
---|
1 | 5.563 s | 600000000 |
2 | 5.606 s | 600000000 |
3 | 5.616 s | 600000000 |
4 | 5.680 s | 600000000 |
5 | 5.636 s | 600000000 |
💡 mutex를 사용하지 않아도 thread-safety 확인 및 대략적으로 5.6초
mutex#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| #include<iostream>
#include<iomanip>
#include<atomic>
#include<thread>
#include<mutex>
#include<chrono>
#include<vector>
const size_t THREAD_NUM = 12;
const size_t COUNT_NUM = 50'000'000;
std::mutex mutex;
int mutex_counter = 0;
void increse_counter_with_mutex() {
for(size_t i = 0; i < COUNT_NUM; i++) {
mutex.lock();
mutex_counter++;
mutex.unlock();
}
}
int main() {
std::vector<std::thread> threads;
std::chrono::steady_clock::time_point start;
std::chrono::steady_clock::time_point end;
double ms = 0.0;
// 스레드 생성 및 join
start = std::chrono::steady_clock::now();
for(size_t i = 0; i < THREAD_NUM; i++) {
threads.emplace_back(increse_counter_with_mutex);
}
for(std::thread &th : threads) {
if(th.joinable()) {
th.join();
}
}
end = std::chrono::steady_clock::now();
ms = (std::chrono::duration_cast<std::chrono::milliseconds>(end - start)).count();
// 결과 출력
std::cout << std::fixed << std::setprecision(3); // 소수점 아래 3자리 반올림
std::cout << "duration: " << ms/1000 << "s\n";
std::cout << "count: " << mutex_counter << "\n";
return 0;
}
|
No. | duration | result |
---|
1 | 32.837 s | 600000000 |
2 | 31.728 s | 600000000 |
3 | 32.469 s | 600000000 |
4 | 31.657 s | 600000000 |
5 | 33.651 s | 600000000 |
💡 5.6초 정도의 소요시간을 보인 std::atomic 과 많은 차이를 보이고, 다수의 스레드가 카운팅을 위해 lock 을 하는 비용이 너무 큰 것으로 보입니다.
✅ 결론#
카운팅과 같은 작은 일에는 std::atomic 사용하여 오버헤드를 줄이는 것이 바람직하다.
🔬 Memory order 테스트#
위 코드에서 메모리 오더만 변경해서 테스트 진행
memory_order_seq_cst#
memory_order_relaxed#
- 28번 라인에서 memory_order 만 relaxed 로 변경
No. | duration | result |
---|
1 | 5.767 s | 600000000 |
2 | 5.575 s | 600000000 |
3 | 5.701 s | 600000000 |
4 | 5.671 s | 600000000 |
5 | 5.637 s | 600000000 |
💡 seq_cst 가 모든 스레드가 동일한 전역 순서를 공유한다고 하여 가장 느릴 것으로 예상했는데 relaxed 와 비슷한 수치를 보임
✅ 결론#
- 단순히 fetch_add 로 카운터만 올릴 때는 결과가 비슷함
- 메모리 오더는 단순히 스레드간 메모리 가시성과 순서를 다룸
- 복잡한 로직이라면 차이가 날 것으로 예상