helio_atomic
std::atomic hi si Test
개요
- C++에서 std::atomic 변수를 여러 스레드에서 사용했을 경우 hi, si가 발생하는지 서버, 노트북(VM)에서 확인하고, 일반 변수 사용할 때, std::atomic 전역 변수 사용할 때 성능(처리 속도)를 알아 본다.
- hi, si: 리눅스 top 명령으로 확인할 수 있는 CPU 항목 중 hi(hardware interrupt) si(software interrupt)를 의미합니다.
- 테스트를 하게 된 원인: 노트북 VM(Linux)에서 helio 테스트 시 hi, si가 상당히 발생하여 성능에 크게 영향을 미치는 것으로 파악했습니다. 소스의 어느 부분(함수)에서 발생하는지 확인하려고 테스트를 진행했습니다.
- 테스트 결과: std::atomic 변수의 경우 '서버'는 hi,si가 발생하지 않았고, '노트북'의 경우 hi가 발생했습니다.
- std::atomic 사용 시 주의사항: 서버에서 hi,si는 발생하지 않았지만, std::atomic 변수 사용 시 일반 변수에 비해 처리 속도가 매우 늦으므로 꼭 필요한 경우만 사용해야 할 것으로 판단됩니다.
테스트
📦 테스트 환경
◎ 서버
- H/W Model: Dell R230
- CPU: Intel(R) Xeon(R) CPU E3-1230 v6 @ 3.50GHz 4c/8t.
- OS: CentOS 7.5.1804 (Core)
- Linux kernel version: 3.10.0 x86_64
- C compiler: g++ (GCC) 8.3.1 20190311 (Red Hat 8.3.1-3)
◎ 노트북
- H/W Model: LG노트북 gram 16ZD90RU-GX56K M/D: 2023.11.
- CPU: Intel i5-1335U(13th) 2.50GHz 코어:10, 논리 프로세서:12, L1 캐시: 928KB, L2: 6.5MB, L3: 12.0MB
- VM: Virtualbox
- Host OS: Windows 11 Pro
- Guest OS: CentOS 9
- Linux kernel version: 5.14.0 x86_64
- C++ compiler: g++ (GCC) 11.5.0 20240719 (Red Hat 11.5.0-2)
📦 std::atomic<uint64_t> 테스트 결과 요약
- 테스트 방법: 스레드 당 1억회 반복: ++counter;
• thread 별 일반 변수 사용
• 전역 std::atomic 변수 사용: 각 thread는 변수를 공유하지 않음
• 전역 std::atomic 변수 사용: 여러 thread가 한 변수를 공유함
- 서버 테스트 결과: thread 수와 관계없이 hi, si가 발생하지 않았다.
- 노트북 테스트 결과: hi가 thread=5 일때 65.8%(500% 기준), 414%(1000% 기준) 발생했다. si는 발생하지 않았다.
◎ 서버
소요시간ms(1회소요시간ns(nano seconds))
| 구분 | thread=1 (1억회) | thread=5 (5억회) | thread=10 (10억회) |
|---|---|---|---|
| thread 별 일반 변수 uint64_t counter0; atomic_int_local.cc |
190ms(1.9ns) | 259ms(0.52ns) | 361ms(0.36ns) |
| 전역 std::atomic<> 변수 각 thread는 변수를 공유하지 않음 atomic_int_thread.cc |
668ms(6.68ns) | 7,546ms(15.09ns) | 15,883ms(15.88ns) |
| 전역 std::atomic<> 변수 여러 thread가 한 변수를 공유함 atomic_int_global.cc |
658ms(6.58ns) | 8,619ms(17.24ns) | 18,750ms(18.75ns) |
◎ 노트북
| 구분 | thread=1 (1억회) | thread=5 (5억회) | thread=10 (10억회) |
|---|---|---|---|
| 전역 std::atomic<> 변수 여러 thread가 한 변수를 공유함 atomic_int_global.cc |
1,681ms(16.8ns) | 27,344ms(54.69ns) | 47,976ms(47.97ns) |
| 구분 | thread=1 (1억회) | thread=5 (5억회) | thread=10 (10억회) |
|---|---|---|---|
| 컴파일 최적화 옵션 -O2 적용 | 1,133ms(11.3ns) | 18,626ms(37.2ns) | 39,990ms(39.9ns) |
컴파일 최적화 옵션
- 'Debug': 기본적으로 최적화 없이 디버깅 정보를 포함 ('-O0')
- 'RelWithDebInfo': 최적화 활성화 + 디버깅 정보 포함 ('-O2')
- 'Release': 최적화 활성화 ('-O3' 또는 플랫폼에 따라 다름)
• Redis는 '-O2', Helio는 '-O3'를 적용하고 있습니다.
- Redis: make Makefile -> OPTIMIZATION?=-O2
- Helio: blaze.sh -> cmake -DCMAKE_BUILD_TYPE=$TARGET_BUILD_TYPE = Debug/Release
📦 std::atomic_flag 테스트 결과 요약
- 테스트 방법: 스레드 당 1억회 반복
std::atomic_flag lock; lock.test_and_set(std::memory_order_acquire); - 서버 테스트 결과: thread 수와 관계없이 hi, si가 발생하지 않았다.
- 노트북 테스트 결과: thread=5 일때 114%(500% 기준) 발생했다. si는 발생하지 않았다.
◎ 서버
| 구분 | thread=1 (1억회) | thread=5 (5억회) |
|---|---|---|
| 전역 std::atomic_flag 변수 | 2,280ms(22.8ns) | 55,322ms(110.6ns) |
◎ 노트북
| 구분 | thread=1 (1억회) | thread=5 (5억회) |
|---|---|---|
| 전역 std::atomic_flag 변수 | 1,124ms(11.2ns) | 109,026ms(218.1ns) |
각 파일 설명
📦 일반 변수 테스트: atomic_int_local.cc
- 개요: 일반 변수 사용 시 성능 테스트, std::atomic 변수와 비교해서 얼마나 차이가 나는지 확인하려고 진행.
- 테스트 방법: 스레드별로 일반 로칼 변수 사용: uint64_t counter0 = 0;
- 서버 테스트 결과: hi,si가 발생하지 않는다. (노트북은 테스트하지 않았음)
- 컴파일: $ g++ -o atomic_int_local atomic_int_local.cc -lpthread
소스 코드
서버 테스트 결과: 소요시간 thread=1,5,10
** std:cout은 소요시간(성능)에 영향이 없다. 1ms도 차이나지 않는다.
** thread=1 보다 소요시간이 136% 더 걸렸으나 이것은 스레드 생성시간이 더 걸린것으로 보인다.
| 회 | thread=1 | thread=5 | thread=10 |
|---|---|---|---|
| 1회 | 190ms | 261ms | 373ms |
| 2회 | 191ms | 260ms | 359ms |
| 3회 | 190ms | 256ms | 358ms |
| 평균(avg) | 190ms | 259ms (136%) |
363ms (191%) |
📦 thread std::atomic 변수 테스트: atomic_int_thread.cc
- 테스트 방법: 한 스레드에서 한 std::atomic 변수만 사용 시 성능 테스트
- 서버 테스트 결과: hi,si가 발생하지 않는다.
- 컴파일: $ g++ -o atomic_int_thread atomic_int_thread.cc -lpthread
소스 코드
서버 테스트 결과: 소요시간 thread=1,2,3,5,10
| 회 | thread=1 | thread=2 | thread=3 | thread=5 | thread=10 |
|---|---|---|---|---|---|
| 1회 | 683ms | 670ms | 3079ms | 7330ms | 15822ms |
| 2회 | 660ms | 670ms | 2707ms | 7518ms | 16128ms |
| 3회 | 662ms | 668ms | 2632ms | 7789ms | 15699ms |
| 평균(avg) | 668ms | 669ms (102%) |
2,806ms (426%) |
7,546ms (1147%) |
15,883ms (2414%) |
📦 전역 std::atomic 변수 테스트(서버): atomic_int_global.cc
- 개요: 여러 스레드에서 전역 std::atomic 변수 1개 사용 시 hi,si 발생 및 성능 테스트
- 테스트 방법: std::atomic<uint64_t> counter; 스레드별로 ++counter; --counter;
- 서버 테스트 결과: hi,si가 발생하지 않는다.
- 컴파일: g++ -o atomic_int_global atomic_int_global.cc
소스 코드
서버 테스트 결과: 소요시간 thread=1,2,3,4,5,10
| 회 | thread=1 | thread=2 | thread=3 | thread=4 | thread=5 | thread=10 |
|---|---|---|---|---|---|---|
| 1회 | 657ms | 2,602ms | 3,871ms | 5,946ms | 9,615ms | 19,772ms |
| 2회 | 658ms | 3,269ms | 5,528ms | 7,876ms | 8,739ms | 19,734ms |
| 3회 | 659ms | 3,277ms | 3,861ms | 7,851ms | 7,502ms | 16,745ms |
| 평균(avg) | 658ms | 3,049ms (463%) |
4,420ms (672%) |
7,224ms (1098%) |
8,619ms (1310%) |
18,750ms (2850%) |
📦 전역 std::atomic 변수 테스트(노트북): atomic_int_global.cc
- 노트북 테스트 결과: hi가 상당히 높게 나타난다.
스레드가 많을수록 hi가 훨씬 더 높게 나타난다.
thread=5: hi+si=65% (500% 기준)
thread=10: hi+si=415% (1000% 기준)
노트북 테스트 결과: thread=5
us+sy=439%(87%), hi+si=65%(13%)
노트북 테스트 결과: thread=10
us+sy=584%(58%), hi+si=415%(41%)
📦 std::atomic_flag lock 테스트
- 개요: 여러 스레드에서 전역 std::atomic_flag 변수 1개 사용 시 hi,si 발생 및 성능 테스트
- 테스트 방법: std::atomic_flag lock; lock.test_and_set() 테스트
소스 코드
◼️ 노트북 테스트 결과
- 노트북 테스트 결과: thread=5 테스트 시 hi+si가 26~114%(500% 기준)까지 발생했다.
- 컴파일: g++ -o atomic_flag atomic_flag.cc
thread=5 초반: 473.8us+1.2sy = 475%(95%) 23.3hi+2.9si = 26.2%(5%)
thread=5 중반: 373.6us+8.0sy = 381%(77%) 112.7hi+1.4si = 114%(23%)
소요시간: thread=1,2,3,4,5
| 회 | thread=1 | thread=2 | thread=3 | thread=4 | thread=5 |
|---|---|---|---|---|---|
| 1회 | 1,119ms | 10,018ms | 32,950ms | 67,132ms | 103,409ms |
| 2회 | 1,130ms | 10,522ms | 33,070ms | 63,889ms | 100,594ms |
| 3회 | 1,122ms | 7,368ms | 35,575ms | 63,003ms | 123,075ms |
| 평균(avg) | 1,124ms | 9,303ms | 33,865ms | 64,675ms | 109,026ms |
◼️ 서버 테스트 결과
- 서버 테스트 결과: hi, si 발생하지 않음.
- 컴파일: g++ atomic_flag.cc -o atomic_flag -lpthread -> 실행 파일 크기: 25888
소요시간: thread=1,5
| 회 | thread=1 | thread=5 |
|---|---|---|
| 1회 | 2,287ms | 56,559ms |
| 2회 | 2,277ms | 55,634ms |
| 3회 | 2,277ms | 53,772ms |
| 평균(avg) | 2,280ms | 55,322ms |
◼️ 서버: 컴파일 최적화 옵션 -O2 적용
- 컴파일: g++ -O2 atomic_flag.cc -o atomic_flag -lpthread -> 실행 파일 크기: 15728
Redis도 컴파일 최적화 옵션 -O2를 적용함 - 테스트 결과: 최적화 옵션 적용 후 많이 빨라짐.
| 회 | thread=1 | thread=5 |
|---|---|---|
| 1회 | 744ms | 44,082ms |
| 2회 | 750ms | 50,429ms |
| 3회 | 745ms | 45,152ms |
| 평균(avg) | 746ms | 46,554ms |
