컴퓨터, CPU의 성능의 정의와 측정법에 대해 알아보겠습니다.

본 글의 내용은 포항공과대학교의 CSED311의 내용을 기반으로
수업에서 다루지 않은 몇몇 내용을 추가하여 구성하였습니다.

컴퓨터의 "성능" 이란?

우리는 소프트웨어와 시스템의 성능을 판단할 때, "빠르다"라는 말을 흔히 사용하고는 합니다. 하지만 정확히 무엇이 빠른걸까요? 시스템의 성능을 정의하는 측정값은 크게 두 가지가 있습니다.

첫번째는 지연시간(Latency) 입니다. 지연시간은 요청 하나가 시작부터 끝까지 걸리는 시간을 의미합니다. 예를 들어 CPU가 명령어 하나를 실행하는 데 걸리는 시간을 생각해보면, 메모리에서 데이터를 읽어오는 load 명령이 있을 때, 그 명령이 발행되고 결과가 레지스터에 도착하기까지의 시간이 지연시간입니다.

두번째는 처리량(Throughput) 입니다. 단위시간 당 프로세서가 처리할 수 있는 작업의 양을 의미합니다. 많은 소스 코드를 컴파일 하는 등 여러 개의 작업을 동시에 처리할 때 처리량이 특히 중요합니다.

언뜻 생각하면 Throughput = 1/Latency의 관계가 성립할 것 같습니다. 각각의 명령어에 소요되는 지연시간이 줄어들면 자연스럽게 처리량도 늘어날테니까요. 하지만 그렇지 않습니다. 대표적인 예시가 슈퍼스칼라 프로세서인데, 이 경우 실행 유닛이 여러개이므로 한번에 여러개의 작업을 처리할 수 있습니다. 개별 명령어의 소요 시간, Latency는 그대로인데 Throughput만 올라가는 것입니다.

시간의 측정

CPU의 성능이란 결국 시간의 문제입니다. 지연시간이 짧으면 당연히 요청에 걸리는 시간이 줄어드니 성능이 늘어나고, 처리량이 높으면 같은 양의 작업을 더 빨리 끝내니까 역시 성능이 높습니다. CPU에서 시간을 측정하는 방법도 여러개가 있는데, UNIX time 명령에서는 아래와 같은 용어를 사용합니다.

  • Wall Clock Time (Elapsed Time) 은 프로그램 실행 버튼을 누른 시간부터 결과가 나올 때까지 "실제로" 흘러간 시간입니다. 프로그램을 사용하는 사용자 입장에서 체감하는 시간이라 가장 직관적이지만, 이 시간 안에는 내가 성능을 측정하고자 하는 프로그램의 시간 뿐만 아니라 다른 프로그램의 처리, 인터럽트 등 다른 것들이 섞여 있습니다.
  • User CPU Time은 CPU가 순수하게 내 코드를 실행하는 데 소모한 시간입니다. 내가 작성한 함수, 반복문, 연산 등이 실제로 CPU에서 돌아간 시간만 해당합니다.
  • System CPU Time은 내 코드가 직접 실행된 것은 아니지만, 내 코드의 실행을 위해 OS 커널이 작동한 시간을 의미합니다. 파일을 읽거나, 메모리를 할당하거나, 네트워크 패킷을 보내는 등의 시스템 콜이 이 시간에 포함됩니다.

Elapsed Time에서 User CPU Time과 System CPU Time을 빼면 약간 남는데, 이건 내 프로그램과 무관하게 소비된 시간입니다. 다른 프로세스가 CPU를 점유했거나, 디스크 I/O를 기다리거나, 스케쥴러에 의해 내 프로세스의 처리 순서가 밀려난 시간 등이 여기에 포함됩니다.

따라서 프로세스의 성능을 측정하고 보고할 때에는, 이 중 뭘 측정한 것인지 정확히 해야합니다. User CPU Time을 보면 내 코드 자체의 효율을 판단할 수 있고, Wall Clock Time을 보면 시스템에서 사용자가 실제로 기다릴 시간을 측정할 수 있습니다.

CPU 클럭과 FLOPS

성능에 큰 영향을 주는 요소로는, CPU의 클럭(Clock) 이 있습니다. CPU 클럭이란 CPU 내부에서 모든 동작의 박자를 맞추는 신호입니다. 1GHz면 1초에 10억번 틱이 발생하고, 한 틱의 길이가 1나노초가 됩니다. 4GHz면 0.25나노초가 되고요. CPU의 모든 연산은 이 틱에 맞춰서 진행되기 때문에, 클럭이 빠르면 같은 사이클이 필요한 작업을 더 빠른 시간 안에 끝낼 수 있습니다.

하지만 클럭만으로 CPU의 성능을 평가할 수는 없습니다. 컴퓨터 프로세서의 철칙(Iron Law)에 따르면, 프로그램의 실행 시간은 세 요소의 곱으로 표현할 수 있습니다.

cpu time = (time/cycle) x (cycles/instruction) * (instructions/program)

이때 cycles/instruction (인스트럭션 당 사이클 개수)를 CPI라고 부르고, (사이클 당 인스트럭션은 IPC) 초당 인스트럭션 개수를 IPS라고 부릅니다. 보통 초당 백만 인스트럭션을 기준으로 하여, MIPS라고 부릅니다. (Million Instructions Per Second)

아이언 법칙을 보면 클럭 주기 (time/cycle)가 성능에 그대로 곱해지므로 클럭 수만 높이면 성능이 늘어날 것 같지만, 클럭 수를 높일 때 파이프라인이 깊어져 CPI가 올라가 오히려 성능이 감소할 수도 있습니다. 또, ISA를 단순화해서 CPI를 줄이면, 같은 일을 하는데 더 많은 명령어가 필요할 수 있습니다. 따라서, 클락, CPI, 인스트럭션 수 셋 중 하나만 보고 프로그램의 성능을 판단할 수 없고, CPU를 최적화할 때는 세 요소의 균형을 항상 함께 생각해야합니다.

과학 계산 분야에서는 FLOPS (FLoating point Operations Per Second) 라는 지표로 성능을 산출하기도 합니다. FLOPS는 컴퓨터가 초당 실행할 수 있는 부동소수점 연산 수를 수치로 나타낸 것인데, 물리 시뮬레이션, 기상 예측, 행렬 연산과 같은 과학 계산 분야에서는 부동소수점 연산이 핵심적이기 때문에 정수 연산 속도 대신 부동소수점 연산 속도를 지표로 사용합니다. 2026년 현재 기준, 일반적인 휴대폰은 1-10 테라플롭스, 기상청이나 연구소에서 사용하는 슈퍼컴퓨터는 수백 페타플롭스 정도의 성능을 가지고 있습니다.

시간 이외의 평가 기준

프로세서 연산에서 주요한 성능 평가 지표가 시간이기는 하지만, 실행 시간만이 성능의 지표가 되는 것은 아닙니다. 현실에서 프로세서는 전력을 소비하고 열을 발생시키기 때문에, 이를 최소화하는 것 또한 중요한 평가지표가 됩니다.

또한, 구현 비용이나 신뢰성, 보안도 프로세서를 평가하는데 중요한 지표입니다. 아무리 좋은 프로세서라도 설계나 제작에 너무 많은 비용이 든다면 실용적이지 않습니다. 시스템이 얼마나 안정적이고 오류 없이 동작하는지도 중요합니다. 예를 들어, 프로세서를 임의로 오버클럭(Overclock)하여 강제로 실행 속도를 높이면 실행 속도는 빨라지겠지만 신뢰성은 낮아질 것입니다.

프로세서의 보안성은 사이드 채널 공격 방어나 메모리 보호 등을 의미하는데, 보안 기능을 추가하면 실행 시간이 길어질 수 있어, 이 또한 프로세서 성능에서 트레이드오프가 됩니다. 한 예로, 2017년에 멜트다운/스펙터 보안 취약점 사태에서 이를 해결하는 과정 중 운영 체제의 여러 최적화를 포기하며 성능이 크게 하락한 사례가 있습니다. 이러한 취약점이 존재하는 CPU와 그렇지 않은 CPU를 이러한 고려 없이 평가하는 것은 올바르지 않을 것입니다.

암달의 법칙

암달의 법칙은 프로그램의 일부를 아무리 빠르게 만들어도, 전체 성능 향상은 그 부분이 차지하는 비율에 제한된다는 법칙입니다.

$$T_{\text{new}} = T_{\text{old}} \times \left((1-f) + \frac{f}{S_f}\right)$$

$$S_{\text{overall}} = \frac{T_{\text{old}}}{T_{\text{new}}} = \frac{1}{(1-f) + \frac{f}{S_f}}$$

위 수식에서 f는 개선 가능한 부분이 전체에서 차지하는 비율이고, $S_{\text{f}}$는 그 부분을 얼마나 빠르게 만들었는지 입니다.

예를 들어, 프로그램 실행 시간의 80%를 차지하는 부분을 4배 빠르게 만들었다고 하면, 실행 시간 향상은 $\frac{1}{(1-0.8) + 0.8/4} = \frac{1}{0.2 + 0.2}$ = 2.5배가 됩니다. 프로그램의 대다수를 차지하는 부분을 4배나 빠르게 했는데 전체는 2.5배 밖에 빨라지지 않은 것입니다. 극단적으로 해당 부분을 무한히 빠르게 해서 실행시간을 0으로 만들더라도, 나머지 20%가 절대 줄어들지 않기 때문에 5배가 한계입니다.

프로그램의 성능 측정

프로그램의 성능을 측정하기 위해, 우리는 컴퓨터에게 일련의 작업을 수행하게 하는 벤치마크를 실행합니다. 어떤 프로그램이 좋은 벤치마크인지는 아래와 같은 기준으로 평가할 수 있습니다.

  • 어떤 작업을 수행할지 워크로드를 명확하게 정의해야합니다.
  • 시간이나 throughput 같은 구체적인 수치적 지표를 내야합니다
  • 재현 가능해야합니다. 비슷한 조건에서 다시 돌리면 비슷한 결과가 나와야 합니다.
  • 이식 가능해야합니다. 다양한 플랫폼으로 이식해서 여러 시스템에서 돌릴 수 있어야 벤치마크를 통한 시스템 간 결과 비교가 의미 있을 것입니다.
  • 의미 있는 출력을 발생하는 등의 방법으로 벤치마크가 정확히 수행되었는지 검증할 수 있어야합니다. 벤치마크가 이러한 정확성을 확인하지 않으면 컴파일러가 무의미한 연산을 생략해버리는 등 실제 성능과 상관 없이 높은 수치를 만들어낼 수 있습니다.
  • 어떤 장치에서 실행할 수 있고, 어떤 최적화를 사용할 수 있는지 명확히 해야합니다. 그렇지 않으면 벤더마다 유리한 조건으로 벤치마크를 돌려서 수치를 부풀릴 수 있습니다.

현재 가장 대표적인 벤치마크에는 SPEC이 있습니다. SPEC은 CPU의 집약적인 연산 성능을 측정하는 업계 표준으로, 프로세서, 메모리 서브시스템, 컴파일러에 부하를 줍니다.

업계가 아닌 일반 소비자 시장에서는 Cinebench, Geekbench, 3DMark 등 실제 컴퓨터 사용환경과 유사한 환경에서 전체 시스템의 성능을 측정하는 벤치마크가 많이 쓰입니다.

벤치마크 성능을 평균 내기

벤치마크 결과를 종합할 때는, 보통 다양한 벤치마크를 여러번 돌려서 평균을 내는 방법으로 평가하게 됩니다. 다만 이러한 평균을 내는 방법에도 여러가지가 있습니다.

산술 평균

산술 평균(Arithmetic Mean) 은 일상 생활에서 가장 흔하게 쓰는 평균 방식으로, 실행 시간들을 전부 더해 개수로 나누는 방식입니다. 단, 이렇게 측정할 경우 실행 시간이 긴 프로그램이 평균을 지배하는 문제가 있습니다.

가중 산술 평균(Weighted Arithmetic Mean) 은 이러한 산술 평균의 문제를 해결하기 위해 각 프로그램의 실행 빈도를 가중치로 반영하여 산술 평균을 내는 방식입니다. 자주 쓰는 프로그램의 성능이 더 크게 반영되어 실제 사용 패턴에 더 가까운 평균을 냅니다.

기하 평균

기하 평균(Geometric Mean) 은 모든 값을 곱한 뒤 n 제곱근을 취하는 평균 방식입니다. 상술한 SPEC이 이러한 방식을 씁니다. 기하 평균의 가장 큰 장점은 각 프로그램의 가중치 비율과 상관 없이 일관된 결과를 낸다는 것입니다.

예를 들어 아래와 같은 두 머신과 프로그램이 있다고 합시다.

머신프로그램 A프로그램 B
X10초100초
Y20초50초

X를 기준으로 성능을 정규화하면 머신 Y는 A = 2.0, B = 0.5가 됩니다. 산술 평균을 내면 1.25가 되므로 머신 Y가 더 느린 것 처럼 보입니다. 반대로 머신 Y를 기준으로 정규화하면 머신 X는 A = 0.5, B = 2.0이 됩니다. 마찬가지로 산술 평균을 내면 1.25이므로 양쪽 다 상대방보다 1.25배 느리다는 모순된 결과가 나옵니다.

기하 평균을 이용하면 이런 문제가 없습니다. 머신 X 기준으로 머신 Y: $\sqrt{2.0 \times 0.5}$ = 1.0, 머신 Y 기준으로 머신 X: $\sqrt{0.5 \times 2.0}$ = 1.0 양 쪽 모두 1.0이므로 두 머신의 성능이 동등하다는 일관된 결론이 나옵니다.

단, 기하 평균은 실행 시간의 종합과는 직접적인 연관이 없어서 평균 결과의 직관적이 해석이 어렵다는 단점이 있습니다.

조화 평균

조화 평균(Harmonic Mean) 은 각 값의 역수의 산술 평균의 역수입니다. 비율 지표를 평균낼 때 적합합니다. 예를 들어 MIPS나 FLOPS처럼 "단위 시간당 처리량"을 평균내야 할 때, 산술 평균을 쓰면 왜곡됩니다. 속도가 10MIPS인 프로그램을 1초 돌리고, 100MIPS인 프로그램을 1초 돌렸다고 산술 평균이 55MIPS라고 하면 실제 성능보다 평균이 과대평가된 것일겁니다. 조화 평균은 느린 쪽에 더 가중치를 주기 때문에 비율 지표에서 보다 정확한 평균을 냅니다.