우리가 CPU 사용률로 쓰는 지표는 매우 오해를 부르기 쉽고 해마다 더 악화되고 있다. %CPU가 실제로 무엇을 측정하는지, 왜 메모리 지연이 이를 왜곡하는지, 그리고 IPC 같은 PMC 지표로 CPU가 실제로 무엇을 하고 있는지 해석하는 방법을 설명한다.
우리가 모두 CPU 사용률에 사용하고 있는 지표는 매우 심하게 오해를 부르며, 해마다 더 나빠지고 있습니다. CPU 사용률이란 무엇일까요? 프로세서가 얼마나 바쁜가를 뜻할까요? 아닙니다, 그것이 이 지표가 측정하는 내용은 아닙니다. 네, 저는 어디에서나, 모든 사람이 사용하는 "%CPU" 지표를 말하고 있습니다. 모든 성능 모니터링 제품에서요. top(1)에서도요.
여러분이 CPU 사용률 90%가 의미한다고 생각할 수 있는 것:
Stalled는 프로세서가 명령어 측면에서 앞으로 진행하지 못하고 있었음을 뜻하며, 보통 메모리 I/O를 기다리기 때문에 발생합니다. 위에 제가 그린 비율(바쁜 시간과 stalled 시간 사이)은 제가 실제 운영 환경에서 보통 보는 모습입니다. 여러분도 대부분 stalled 상태일 가능성이 높지만, 그것을 모르고 있을 수 있습니다.
이것이 여러분에게 무엇을 의미할까요? CPU가 얼마나 많이 stalled 상태인지 이해하면, 성능 튜닝 노력을 코드 감소 쪽에 둘지 메모리 I/O 감소 쪽에 둘지 방향을 잡을 수 있습니다. CPU 성능을 살펴보는 사람이라면 누구나, 특히 CPU를 기준으로 자동 확장하는 클라우드 환경에서는, 자신의 %CPU 중 stalled 성분이 얼마나 되는지 알면 도움이 됩니다.
우리가 CPU 사용률이라고 부르는 지표는 사실상 "유휴가 아닌 시간(non-idle time)"입니다. 즉 CPU가 idle thread를 실행하지 않았던 시간입니다. 운영 체제 커널(무엇이든 간에)은 보통 문맥 전환 중에 이것을 추적합니다. idle이 아닌 thread가 실행되기 시작해서 100밀리초 후 멈췄다면, 커널은 그 전체 시간 동안 해당 CPU가 사용되었다고 간주합니다.
이 지표는 시분할 시스템만큼이나 오래되었습니다. Apollo Lunar Module guidance computer(선구적인 시분할 시스템)는 idle thread를 "DUMMY JOB"이라고 불렀고, 엔지니어들은 그것을 실행한 주기와 실제 작업을 실행한 주기의 비율을 중요한 컴퓨터 사용률 지표로 추적했습니다. (저는 이것에 대해 이전에 쓴 적이 있습니다.)
그렇다면 무엇이 문제일까요?
오늘날 CPU는 주 메모리보다 훨씬 더 빨라졌고, 메모리를 기다리는 시간이 여전히 "CPU 사용률"이라고 불리는 것의 대부분을 차지합니다. top(1)에서 높은 %CPU를 보면, 여러분은 프로세서를 병목으로 생각할 수 있습니다. 즉 방열판과 팬 아래 있는 CPU 패키지 말입니다. 하지만 실제 병목은 DRAM 뱅크들일 수 있습니다.
이 문제는 점점 더 심해지고 있습니다. 오랫동안 프로세서 제조사들은 DRAM 접근 지연 시간이 개선되는 속도보다 더 빠르게 클록 속도를 높여 왔습니다("CPU DRAM gap"). 이것은 2005년경 3 GHz 프로세서 부근에서 완화되었고, 그 이후로는 프로세서가 더 많은 코어와 하이퍼스레드, 그리고 멀티 소켓 구성으로 확장되면서 메모리 서브시스템에 더 큰 부담을 주게 되었습니다. 프로세서 제조사들은 더 크고 더 똑똑한 CPU 캐시, 더 빠른 메모리 버스와 인터커넥트로 이 메모리 병목을 줄이려 해왔습니다. 하지만 우리는 여전히 대개 stalled 상태입니다.
Performance Monitoring Counters(PMCs)를 사용하면 됩니다. 이것은 Linux perf 및 다른 도구로 읽을 수 있는 하드웨어 카운터입니다. 예를 들어 전체 시스템을 10초 동안 측정해 보겠습니다:
Performance counter stats for 'system wide':
641398.723351 task-clock (msec) # 64.116 CPUs utilized (100.00%)
379,651 context-switches # 0.592 K/sec (100.00%)
51,546 cpu-migrations # 0.080 K/sec (100.00%)
13,423,039 page-faults # 0.021 M/sec
1,433,972,173,374 cycles # 2.236 GHz (75.02%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
1,118,336,816,068 instructions # 0.78 insns per cycle (75.01%)
249,644,142,804 branches # 389.218 M/sec (75.01%)
7,791,449,769 branch-misses # 3.12% of all branches (75.01%)
10.003794539 seconds time elapsed
여기서 핵심 지표는 instructions per cycle(insns per cycle: IPC)입니다. 이것은 평균적으로 각 CPU 클록 사이클마다 몇 개의 명령어를 완료했는지를 보여 줍니다. 높을수록 좋습니다(단순화해서 말하면). 위의 0.78이라는 예는 나쁘지 않아 보일 수 있습니다(78% 바쁨?) 하지만 이 프로세서의 최고 속도가 IPC 4.0이라는 점을 떠올리면 다르게 보입니다. 이것은 _4-wide_라고도 하는데, 명령어 fetch/decode 경로를 가리키는 표현입니다. 즉 이 CPU는 클록 사이클마다 네 개의 명령어를 retire(완료)할 수 있습니다. 따라서 4-wide 시스템에서 IPC가 0.78이라는 것은, CPU가 최고 속도의 19.5%로 동작하고 있음을 뜻합니다. 더 새로운 Intel 프로세서는 5-wide로 이동할 수도 있습니다.
더 깊이 파고들 수 있는 PMC는 수백 가지가 더 있습니다. 예를 들어 stalled 사이클을 유형별로 직접 측정할 수 있습니다.
가상 환경에 있다면, 하이퍼바이저가 게스트에 이를 지원하는지에 따라 PMC에 접근하지 못할 수 있습니다. 저는 최근 The PMCs of EC2: Measuring IPC에 대해 글을 올렸는데, AWS EC2의 Xen 기반 클라우드에서 이제 전용 호스트 유형에 대해 PMC를 사용할 수 있게 되었음을 보여 줍니다.
만약 여러분의 IPC가 < 1.0이라면, 메모리 stalled 상태일 가능성이 높으며, 소프트웨어 튜닝 전략에는 메모리 I/O 줄이기, CPU 캐싱과 메모리 지역성 향상, 특히 NUMA 시스템에서의 개선이 포함됩니다. 하드웨어 튜닝에는 더 큰 CPU 캐시를 가진 프로세서, 더 빠른 메모리, 버스, 인터커넥트를 사용하는 것이 포함됩니다.
만약 여러분의 IPC가 > 1.0이라면, 명령어에 의해 제한되는 상태일 가능성이 높습니다. 코드 실행을 줄일 방법을 찾아보십시오. 불필요한 작업 제거, 연산 캐시 등입니다. CPU flame graphs는 이런 조사를 위한 훌륭한 도구입니다. 하드웨어 튜닝으로는 더 빠른 클록 속도와 더 많은 코어/하이퍼스레드를 시도해 보십시오.
위의 제 규칙에서는 IPC 1.0을 기준으로 나누었습니다. 이 값은 어디서 나온 것일까요? 제가 PMC를 다뤄 온 이전 경험을 바탕으로 만든 값입니다. 여러분의 시스템과 실행 환경에 맞는 맞춤값을 얻는 방법은 이렇습니다. 하나는 CPU bound이고 다른 하나는 memory bound인 두 개의 더미 워크로드를 작성하십시오. 그들의 IPC를 측정한 뒤, 중간값을 계산하면 됩니다.
모든 성능 도구는 %CPU와 함께 IPC를 보여 주어야 합니다. 또는 %CPU를 instruction-retired cycles와 stalled cycles로 분해해, 예를 들어 %INS와 %STL을 보여 주어야 합니다.
그리고 top(1)에 대해서는, Linux용으로 프로세스별 IPC를 보여 주는 tiptop(1)이 있습니다:
tiptop - [root] Tasks: 96 total, 3 displayed screen 0: default
PID [ %CPU] %SYS P Mcycle Minstr IPC %MISS %BMIS %BUS COMMAND 3897 35.3 28.5 4 274.06 178.23 0.65 0.06 0.00 0.0 java 1319+ 5.5 2.6 6 87.32 125.55 1.44 0.34 0.26 0.0 nm-applet 900 0.9 0.0 6 25.91 55.55 2.14 0.12 0.21 0.0 dbus-daemo
CPU 사용률을 오해하게 만드는 것은 메모리 stall 사이클만이 아닙니다. 다른 요인들도 있습니다:
이 글에는 여기(아래)와 다른 곳들(1, 2)에서 수백 개의 댓글이 달렸습니다. 시간을 들여 이 주제에 관심을 가져 준 모든 분께 감사드립니다. 제 답변을 요약하면 이렇습니다. 저는 iowait에 대해 전혀 말하고 있는 것이 아닙니다(그것은 디스크 I/O입니다). 그리고 여러분이 메모리 제한 상태라는 것을 안다면 실행 가능한 조치들이 있습니다(위 참조).
하지만 CPU 사용률은 실제로 잘못된 것일까요, 아니면 그저 매우 오해를 부르는 것일까요? 많은 사람들이 높은 %CPU를 보고 처리 장치가 병목이라고 해석한다고 저는 생각합니다. 그것은 잘못된 해석입니다(앞서 말했듯이). 그 시점에서는 아직 알 수 없고, 실제로는 외부의 어떤 것인 경우가 많습니다. 그렇다면 이 지표는 기술적으로는 맞는 것일까요? CPU stall 사이클을 다른 어떤 것도 사용할 수 없다면, 그것은 결국 "기다리느라 사용 중(utilized waiting)"이라고 볼 수 있지 않을까요(모순어법처럼 들리지만)? 어떤 경우에는 그렇습니다. OS 수준 지표로서의 %CPU는 기술적으로 맞지만 매우 오해를 부른다고 말할 수 있습니다. 하지만 하이퍼스레드가 있으면, 이제 그 stalled 사이클을 다른 thread가 사용할 수 있으므로, %CPU는 실제로는 사용 가능했던 사이클을 사용 중으로 셀 수 있습니다. 그것은 잘못된 것입니다. 이 글에서는 해석 문제와 제안하는 해결책에 초점을 맞추고 싶었지만, 네, 이 지표에는 기술적인 문제도 있습니다.
Adrian Cockcroft가 이전에 논의했듯이, 사용률이라는 지표 자체가 이미 망가져 있었다고 말할 수도 있습니다.
CPU 사용률은 매우 오해를 부르는 지표가 되었습니다. 여기에는 주 메모리를 기다리는 사이클이 포함되며, 이것이 현대 워크로드를 지배할 수 있습니다. 어쩌면 %CPU는 cycles의 약자인 %CYC로 이름을 바꾸어야 할지도 모릅니다. %CPU가 실제로 무엇을 의미하는지는 instructions per cycle(IPC) 같은 추가 지표를 사용해 파악할 수 있습니다. IPC < 1.0이면 메모리 제한 상태일 가능성이 높고, IPC > 1.0이면 명령어 제한 상태일 가능성이 높습니다. 저는 이전 글에서 IPC를 다뤘고, 이를 측정하는 데 필요한 Performance Monitoring Counters(PMCs) 입문도 포함했습니다.
%CPU를 보여 주는 성능 모니터링 제품, 즉 사실상 모든 제품은, 그 의미를 설명하고 최종 사용자를 오해시키지 않기 위해 PMC 지표도 함께 보여 주어야 합니다. 예를 들어 IPC와 함께 %CPU를 보여 주거나, instruction-retired cycles와 stalled cycles를 함께 보여 줄 수 있습니다. 이런 지표를 갖추면 개발자와 운영자는 자신의 애플리케이션과 시스템을 더 잘 튜닝할 방법을 선택할 수 있습니다.