Reddit

Tuning a Server for Benchmarking

벤치마크 측정값의 잡음을 줄이는 서버 튜닝

같은 벤치마크도 CPU 스케줄링과 클록 변화에 따라 실행 시간이 달라집니다. 코어 고정, 주파수 governor 설정, SMT와 turbo 비활성화 순으로 서버를 조정해 변동계수(CV)를 2.72%에서 0.26%로 낮춘 과정을 보여줍니다.

AI 요약

코드 최적화는 측정에서 시작하지만, 측정값에 잡음이 크면 작은 개선을 알아보기 어렵습니다. 글은 반복 가능한 측정 환경을 만드는 일이 실제 서비스의 성능을 최대화하는 튜닝과 다르다고 먼저 짚습니다. 벤치마크에서는 최고 속도보다 실행 간 일관성이 중요합니다. 반대로 운영 서버에서는 가능한 한 빠른 실행이 목표입니다.

짧은 연산도 측정 잡음에 흔들립니다

예제로 C++ 배열 합산 벤치마크를 만듭니다. 4096개의 double 값을 256회 합산하고, 각 반복 사이에는 2밀리초 동안 대기합니다. 대기 시간은 측정에서 제외하며, DoNotOptimize로 컴파일러가 합산 코드를 지우지 못하게 합니다. -O3, -march=native, -mtune=native, -flto, -ffast-math 옵션으로 빌드한 뒤 10회 반복하자 평균은 99.6마이크로초, 표준편차는 2.70마이크로초, 변동계수(CV)는 2.72%입니다. CV는 표준편차를 평균으로 나눈 값입니다. 이 정도 잡음이 있으면 2% 개선은 측정 결과에서 구분하기 어렵습니다.

하드웨어 구조를 확인하고 코어를 고정합니다

lstopo로 캐시와 코어, SMT 구성, PCIe 장치 배치를 확인합니다. 하이브리드 CPU에서는 어느 코어에서 실행하느냐에 따라 성능이 달라질 수 있습니다. 예시 노트북에서는 CPU 4가 낮은 클록의 E-core이고, CPU 12에서는 L3 캐시를 사용할 수 없습니다. 반면 모든 코어가 동등한 서버는 벤치마크 환경으로 다루기 수월합니다. I/O 작업을 측정한다면 NVMe나 NIC가 어느 PCIe 경로와 NUMA 노드에 연결됐는지도 살펴야 합니다.

Linux 스케줄러는 작업을 다른 코어로 옮길 수 있습니다. 코어가 바뀌면 캐시가 차가워지고, 하이브리드 CPU에서는 코어 종류에 따라 실행 속도도 달라집니다. taskset -c 2로 벤치마크를 한 코어에 고정하자 평균 실행 시간은 55.3마이크로초, CV는 1.06%가 됐습니다. 반복 실행마다 같은 코어를 깨우면서 클록이 낮아지는 현상도 줄었습니다. 코어 고정은 다른 작업을 그 코어에서 몰아내지는 않습니다. 공유를 막으려면 isolcpus, nohz_full, rcu_nocbs 커널 옵션을 쓰거나 cpuset cgroup으로 코어를 예약할 수 있습니다.

주파수 변화와 SMT 간섭을 줄입니다

Linux는 부하에 따라 CPU 주파수를 조절합니다. 기본 설정에서는 벤치마크가 낮은 클록에서 시작해 실행 중 주파수가 올라갈 수 있습니다. cpupower frequency-set --governor performance로 performance governor를 설정하면 평균은 54.9마이크로초, CV는 0.79%가 됩니다. 코어 고정이 클록을 이미 따뜻하게 유지해 개선 폭은 작지만, 코어 고정 없이 governor만 바꾸면 기준 평균 99.6마이크로초가 54.5마이크로초로 줄었다고 설명합니다.

SMT를 켜면 같은 물리 코어의 형제 스레드가 실행 유닛과 L1/L2 캐시를 공유합니다. 해당 스레드에 다른 작업이 배정되면 측정에 간섭할 수 있습니다. echo off | sudo tee /sys/devices/system/cpu/smt/control로 SMT를 끄자 평균은 55.3마이크로초, CV는 0.26%로 내려갑니다. 코어의 실행 유닛과 캐시를 벤치마크가 단독으로 쓰게 된 결과입니다.

turbo는 측정 조건에 따라 끕니다

performance governor를 사용해도 온도와 전력 한도에 따라 turbo 주파수는 달라질 수 있습니다. 글에서는 echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost로 turbo를 비활성화합니다. 예시 서버의 짧은 작업은 turbo가 작동할 만큼 길지 않아 평균에 변화가 거의 없습니다. turbo가 작동하는 장비에서는 최고 성능을 포기하므로 평균 실행 시간이 늘 수 있지만, 반복 측정 간 비교는 더 쉬워집니다. 지연 시간에 민감한 운영 환경에서는 반대로 turbo를 유지하며, 일부 저지연 거래 시스템은 냉각을 강화하고 모든 코어의 주파수를 높고 일정하게 고정한다고 덧붙입니다.

단계별 결과와 재현성

튜닝 단계별 결과는 다음과 같습니다. 기준 상태는 평균 99.6마이크로초, 표준편차 2.70마이크로초, CV 2.72%입니다. 코어 고정 뒤 55.3마이크로초와 1.06%, performance governor 적용 뒤 54.9마이크로초와 0.79%, SMT 비활성화 뒤 55.3마이크로초와 0.26%를 기록합니다. 마지막으로 turbo를 꺼도 평균은 55.5마이크로초, CV는 0.26%로 거의 달라지지 않습니다. 전체적으로 평균 실행 시간은 약 1.8배 빨라졌고 CV는 0.26%까지 낮아졌습니다. 다만 turbo 비활성화가 모든 시스템에서 같은 결과를 내지는 않습니다.

더 바쁜 서버에서는 ASLR, NMI watchdog, Transparent Huge Pages 설정도 살펴볼 만하다고 소개합니다. 글의 bench-remote.sh 스크립트는 관련 설정을 적용합니다. 이런 변경은 재부팅 뒤 유지되지 않으며, 측정을 마친 뒤 정상 상태로 되돌아가는 구성이 의도에 맞습니다.

Reddit 반응

  • @u/Adept_Percentage6893 — 재현성과 잡음에 관한 부분이 글의 주제인 듯하고, 그 점은 좋습니다. 덧붙일 말이 있습니다. 제대로 개발한다면 언제나 가능한 한 성능 좋은 프로그램을 쓰려고 합니다. 가끔은 대부분의 사용자가 마주칠 상황을 생각해 알고리즘을 결정해야 합니다. 여러 방식으로 처리할 수 있어도 하나를 골라야 합니다. 그 선택이 소수 사용자에게 불리하게 작용할 가능성이 충분하다면, 애플리케이션이나 플랫폼에 그들이 예외적인 상황에 놓였다고 알릴 튜너블 설정을 제공해야 합니다. 성능 튜닝은 90%가 측정이고 10%가 이런 튜너블을 찾는 일입니다. 이 글은 애플리케이션을 일종의 블랙박스로 다룹니다. 하지만 제공하려는 네트워크 서비스의 성능 특성을 이해하면 사용자가 중요하게 여길 성능 특성에 영향을 주는 설정부터 우선순위를 매길 수 있습니다. 그렇지 않으면 지표 A를 개선하면서 지표 B에는 거의 영향이 없는 설정을 바꿨는데, 사용자는 지표 A를 중요하게 여기지 않고 작업이 합리적인 시간 안에 끝나기만 하면 된다는 사실을 뒤늦게 알 수 있습니다. 백그라운드에서 실행하는 주기적 유지보수 작업이 그런 예입니다. 애플리케이션 전체가 아니라 중요한 코드 경로, 특히 자주 실행되는 경로를 최적화해야 합니다. 중요한 경로의 병목을 알면 플랫폼 설정도 더 쉽게 찾을 수 있습니다.

원문: David Alvarez Rosa / 번역·요약: Trawling