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



Email 답글이 올라오면 이메일로 알려드리겠습니다.