CPU 사용률을 나타내는 2가지 수치 - 사용량(Usage)과 활용률(Utilization)
예전에 아래의 글을 쓰면서,
C# - 시스템의 CPU 사용량 및 프로세스(EXE)의 CPU 사용량 알아내는 방법
; https://www.sysnet.pe.kr/2/0/1684
CPU 사용률을 GetSystemTimes Win32 API를 이용해 구했고 그 수치가 "성능 모니터링 도구(Perfmon.msc)"를 시작했을 때 기본으로 보여주는 성능 카운터와 같았다고 했습니다.
[아래의 PerfCounter는 동일한 값을 반환]
Object: Processor Information
Counter: % Processor Time
Instance: _Total
Object: Processor
Counter: "%Processor Time
Instance: _Total)
그리고, 작업 관리자(좀 더 정확히는 [프로세스] 탭 및 [성능] 탭)의 CPU 수치는 아래의 성능 카운터와 같습니다.
[작업 관리자가 보여주는 CPU 사용률]
Object: Processor Information
Counter: % Processor Utility
Instance: _Total
위의 2가지 수치에 대해 나름 의미를 해석해 주는 글이 있어서 소개할까 하는데요, ^^
CPU 이용률의 두 가지 얼굴 – CPU 코어 사용량(Usage)과 활용률(Utilization)
; https://netmarble.engineering/cpu-core-usage-and-utilization/
그러니까, 위의 글에 따르면 성능 모니터링 도구에서 보여준 수치는 "CPU 사용량(Usage)"이고, 작업 관리자에서 봤던 (대체로 좀 더 높게 나오는) CPU 수치는 "CPU 활용률(Utilization)"이라는 것입니다.
- CPU 사용량(Usage) - 시간 기반(Time-based)으로 측정되는 메트릭
- CPU 활용률(Utilization) - 주파수 기반(Frequency-based)으로 측정되는 메트릭
그런데, 위의 글에서 소개하는 마이크로소프트의 공식 문서에 따르면,
CPU usage exceeds 100% in Task Manager and Performance Monitor if Intel Turbo Boost is active
; https://learn.microsoft.com/en-us/troubleshoot/windows-client/performance/cpu-usage-exceeds-100
후자의 경우 주파수를 이용한 설명은 해도 "주파수 기반"이라는 용어는 사실 등장하지 않습니다. 그보다는 다음과 같이 2개의 기준으로 분류하고 있습니다.
- time-based performance counters - measure the percentage of time that the processor is busy
- utility performance counters - measure how much work the processor actually performs
마이크로소프트의 저 문서를 보니까, 이제야 좀 2개의 수치에 대한 차이가 눈에 들어옵니다. ^^ 간단하게 설명해 볼까요?
우선, time-based는 말 그대로 CPU가 (속도와 상관없이) 일하고 있는 시간을 기준으로 측정한 값입니다. 계산을 간단하게 하기 위해 단일 코어 CPU가 1초 동안 500ms만큼 busy 상태로 있었다면 50%의 사용률을 나타내는 것입니다.
반면 utility는 같은 시간을 소모했더라도 CPU의 실행 주파수가 달라지는 것을 반영한 값입니다. 예를 들어, (CPU 구매 시 표기된) 명목 주파수가 1GHz인 CPU가 Turbo Boost 상태의 2GHz로 1초 동안 내내 실행되었다면 200%까지 일을 한 것입니다. (하지만 작업 관리자는 100%로 절삭합니다.)
결국 주파수가 변하지 않는 CPU였다면 time-based와 utility는 같은 값을 나타낼 수 있었겠지만, 동작 주파수가 달라지는 근래의 환경에서는 저 2개의 수치가 상이해질 수밖에 없는 것입니다.
대충 이해가 되셨죠? ^^
"
CPU 이용률의 두 가지 얼굴 – CPU 코어 사용량(Usage)과 활용률(Utilization)" 글 덕분에 2개의 차이점을 쉽게 알 수 있었는데요, 대신 해당 글에서 약간 동의할 수 없는 내용이 있어 이에 대한 부가적인 기록을 남깁니다.
우선, time-based 수치를 설명하면서 들었던 공식을 보면,
%ProcessorTime=1−(B/T)
B : 측정 범위 시간 내에 논리 프로세서에서 "유휴(Idle) 스레드"가 소비하는 100ns 간격의 수(샘플링 횟수)
T : 측정 범위 시간 내에 100ns 간격의 수 (샘플링 횟수)
위의 내용에 대한 참고 문서를 밝히지 않아 현재는 전적으로 신뢰할 수 없는 내용으로 보입니다. 사실, 커널의 스레드 스케줄러는 하드웨어 자원인 CPU에 Idle 스레드를 얹고 해제하는 것을 담당합니다. 즉, 정확히 그 시간을 알고 있기 때문에 굳이 100ns 간격으로 샘플링을 해야 할 필요가 없습니다.
성능 모니터링 도구의 설명에서도,
It is calculated by measuring the percentage of time that the processor spends executing the idle thread and then subtracting that value from 100%.
딱히 100ns나 sampling 횟수라는 단어는 쓰고 있지 않습니다. 혹시 이에 대해 공식 문서를 알고 계시다면 덧글 부탁드립니다. ^^
그다음으로, utility 수치를 구하면서 제시한 설명을 보면,
%ProcessorUtility=(E/B)
E : 주어진 시간 범위 내에 논리 프로세서의 유효 주파수(Processor Effective Frequency)
B : 논리 프로세서의 기본 주파수(Processor Base Frequency)
유효 주파수는 실제 동작한 CPU 클럭 사이클 수를 의미하며 RDTSC 함수를 이용해서 계산할 수 있고, 기본 주파수 정보는 PROCESSOR_POWER_INFORMATION 구조체를 인자로 한 NtPowerInformation API 호출을 통해서 구할 수 있습니다.
"E" 수치를 rdtsc로 계산할 수 있다고 했는데요, 사실 이것은 전에도 제가 설명했듯이,
윈도우 운영체제의 시간 함수 (5) - TSC(Time Stamp Counter)와 QueryPerformanceCounter
; https://www.sysnet.pe.kr/2/0/11068
근래의 CPU는 모두 Invariant TSC를 지원하기 때문에 CPU의 실제 동작 주파수에 상관없이 rdtsc는 보정된 값을 반환합니다. 게다가, rdtsc는 유효 명령어 수행과 관계없이 설정된 주파수로 증가하기 때문에 이 값을 분자로 둘 수 없습니다. 이에 대한 테스트를 다음의 코드로 확인하는 것도 가능합니다.
#include <stdio.h>
#include <Windows.h>
#include <ctime>
#pragma comment(lib, "winmm.lib")
int main()
{
int count = 100;
const DWORD msDuration = 1000;
const int iterations = 36;
for (int i = 0; i < iterations; ++i)
{
__int64 rdtscStart = __rdtsc(); // 현재의 cpu cycle 수를 구하고,
Sleep(1000);
__int64 rdtscElapsed = __rdtsc() - rdtscStart; // 1초가 지난 후 cpu cycle 수를 구함
printf(" elapsed: %I64d, %0.4fGHz\n", rdtscElapsed, rdtscElapsed / 1000.0 / 1000 / 1000);
}
return 0;
}
위와 같이 Thread Sleep을 1초 정도 준 다음 그 사이의 rdtsc를 값을 구하면, 제 컴퓨터의 i9-12900K CPU 환경에서는 다음과 같은 결과가 나옵니다.
elapsed: 3187455039, 3.1875GHz
elapsed: 3190156222, 3.1902GHz
elapsed: 3188967666, 3.1890GHz
elapsed: 3187236801, 3.1872GHz
elapsed: 3191939036, 3.1919GHz
elapsed: 3188609006, 3.1886GHz
...[생략]...
보시다시피 "
Performance-core 기본 주파수"에 해당하는 3.2GHz로 동작하고 있습니다.
따라서, 결국 저 공식은 3.2/3.2로 계산이 돼 100% CPU 사용률이라는 잘못된 결과가 나옵니다. 이를 통해 "E"는 rdtsc로 구한 값이라기보다는 오히려 "Effective"라는 말에 유의해 해석해야 합니다. 즉, CPU가 운영체제에 의해 할당된 작업을 수행하는 용도로 순수하게 소비한 주파수만을 유효하게 계산식에 포함할 때 의미가 있는 것입니다.
간단하게 예를 들면, 2GHz의 명목 주파수를 갖는 CPU가 지난 1초 동안 5GHz로 동작했는데 그중 500ms 동안 운영체제에 의해 작업이 할당돼 실행이 되었다면 실질적으로 2.5GHz에 해당하는 주파수가 명령어를 수행하는 데 사용됐으므로 2.5/2로 계산해 125%의 사용률이 나오는 것입니다.
마지막으로 한 가지만 더 문제를 제기한다면, 해당 글에서는 저렇게 time-based/utility 메트릭을 이해해야 하는 이유를 마지막 즈음에 "모니터링" 절을 통해 이렇게 설명하고 있습니다.
앞서 설명해 드린 것처럼, CPU 코어 사용량(Usage)은 논리 프로세서가 스레드 — System Idle Process가 아닌 Application Process가 생성한 스레드 ― 실행에 소비하는 시간을 의미합니다. 이는 논리 프로세서에서 실행 가능한 스레드(Ready or Running 상태의 스레드)가 소비하는 시간을 나타냅니다. 따라서 CPU 코어 사용량(Usage)만으로는 논리 프로세서에서 “대기(Waiting)” 상태의 스레드와 관련된 CPU 자원 소모 정보를 확인할 수 없습니다. 그러므로 CPU 코어 활용률(Utilization) 메트릭을 이용하여 “대기(Waiting)” 상태의 스레드와 관련된 불필요한 CPU 자원 소모 여부를 확인하는 것이 중요합니다.
왜? utility 수치가 Waiting 상태의 스레드와 관련된 불필요한 CPU 자원 소모와 연결이 되는 것일까요? 운영체제는 사용자 모드의 코드를 수행하는 것뿐만 아니라 커널 코드까지 포함해 time-based/utility에 대한 CPU 사용량을 제공합니다. 즉, Waiting 상태의 스레드가 많다고 해서 특별히 utility 메트릭의 값이 time-based에 비해 바뀐 값을 나타내는 것은 아닙니다.
개인적으로 위의 내용을 정리하면서, 한 가지 궁금한 점이 생겼습니다. 우선, time-based 메트릭의 경우는 운영체제가 구간 사이의 흐른 시간 및 스레드를 CPU에 할당한 시간을 알고 있다는 점에서, 계산 과정이 쉽게 납득이 갑니다.
그런데, 주파수는 이야기가 좀 다릅니다. 운영체제 입장에서 현재 CPU(코어)의 동작 주파수를 정확히 알고 있어야만 하는데, 이게 가능하려면 1) CPU가 주파수를 조정할 때마다 운영체제에게 Interrupt로 알리든가, 2) 운영체제가 애당초 CPU의 주파수도 조정하든가... 해야 합니다.
하드웨어를 잘 아시는 분은 ^^ 알고 계시겠지만, 소프트웨어 개발자인 저는 저 부분을 몰라서 검색해야만 했습니다. 그 결과, 아래의 글을 찾았는데요,
System crashes and you cannot change CPU frequency on Intel SKL platform in Windows 8.1 or Windows Server 2012 R2
; https://support.microsoft.com/en-us/topic/system-crashes-and-you-cannot-change-cpu-frequency-on-intel-skl-platform-in-windows-8-1-or-windows-server-2012-r2-529d5e8b-0c1b-4568-dc54-21eccc2f23a7
The operating system cannot change CPU frequency to reduce power consumption. Therefore, CPU always runs on the maximum possible frequency that may affect the device battery life and heat dissipation.
To resolve this issue, we have released a hotfix that contains the Intelppm.sys file.
Note The Intelppm.sys file is the CPU driver of Skylake. There are other drivers in the same installation component with the Intelppm.sys file in this hotfix.
즉, 윈도우 운영체제가 (인텔의 경우) Intelppm.sys 드라이버를 이용해 CPU의 동작 주파수를 조정한다는 것입니다. 만약 CPU 스스로 조절하는 것이었다면 "CPU always runs on the maximum possible frequency"라는 문구는 없었겠죠? ^^
보다 공식적인 문서가 있나 좀 더 검색을 해봤는데요,
Enhanced Intel® SpeedStep® Technology for the Intel® Pentium® M Processor
; https://download.intel.com/design/network/papers/30117401.pdf
All Intel Pentium M processors have support for Enhanced Intel SpeedStep Technology. This can be
verified by checking the ECX feature bit 07 in the CPUID register. Enhanced Intel SpeedStep Technology
is enabled by setting the IA32_MISC_ENABLE MSR, bit 16. This bit should be written by the system
BIOS upon boot.
Processor frequency/voltage transitions are initiated by writing a 16-bit value to the IA32_PERF_CTL
register. If a transition is already in progress, transition to a new value will take effect subsequently. Reads
of the IA32_PERF_CTL register determine the last targeted operating point. The current operating point
can be read from IA32_PERF_STATUS register, which is updated dynamically.
By centralizing the implementation of Enhanced Intel SpeedStep Technology to the processor, software can
perform a frequency/voltage operating state transition by simply writing to one register
저렇게 Intel 측에서 제공하는 레지스터에 값을 기록함으로써 주파수(및 동작 전압)을 조절할 수 있다고 합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]