하드웨어 고급 #1 CPU 마이크로아키텍처와 perf — 같은 100%가 다른 이유
하드웨어 중급에서 네 자원의 지표를 읽고 병목을 가려내는 진단법을 잡았습니다. 고급 시리즈는 그 진단을 양쪽으로 확장합니다. 기초가 개념이고 중급이 진단이었다면, 고급은 아래로는 커널과 실리콘 수준의 관측으로 내려가고, 위로는 서버가 모여 사는 데이터센터로 올라갑니다.
시리즈는 7편입니다. CPU 마이크로아키텍처와 perf(1편), eBPF 관측(2편), 페이지 캐시·hugepages·메모리 대역폭(3편), ZFS 심화(4편)까지가 커널 아래쪽이고, 데이터센터 전력(5편), 냉각과 랙(6편), 펌웨어·BMC와 서버 수명주기(7편)가 데이터센터 위쪽입니다.
첫 편의 질문은 이것입니다. 사용률 100%인 두 서버에서, 두 CPU는 같은 양의 일을 하고 있을까요? 답은 “아닐 수 있다"이고, 그 차이를 숫자로 보여 주는 도구가 perf입니다. perf를 포함한 진단 도구의 전체 지형은 RHEL 고급 #3이 다루므로, 이 글은 도구 사용법이 아니라 그 출력에 찍힌 숫자를 마이크로아키텍처로 해석하는 법에 집중하겠습니다.
사용률 100%의 두 얼굴 — IPC #
OS가 보는 CPU 사용률은 “코어에 작업이 올라가 있던 시간의 비율"입니다. 코어가 그 시간에 실제로 명령어를 처리했는지, 아니면 메모리에서 데이터가 오기를 기다리며 공회전했는지는 구분하지 않습니다. 메모리를 기다리는 동안에도 작업은 코어를 점유하고 있어서, 사용률에는 둘 다 100%로 찍힙니다.
이 차이를 드러내는 지표가 IPC(instructions per cycle, 클럭 사이클당 처리한 명령어 수)입니다. 현대 서버 CPU는 사이클당 4개 이상의 명령어를 처리할 능력이 있으므로, 대략 이런 감각으로 읽습니다.
- IPC가 1.0을 크게 밑돌면, 코어가 일보다 대기에 사이클을 쓰고 있다는 신호입니다. 대개 메모리 접근이 원인입니다.
- IPC가 1.0을 크게 웃돌면, 코어가 연산으로 차 있다는 신호입니다. 이때의 100%는 진짜 연산 포화입니다.
같은 100%라도 IPC 0.5와 2.0은 코어가 해낸 일의 양이 4배 차이입니다. 처방도 갈립니다. 전자는 코어를 늘려도 대기만 늘어나고, 후자는 코어 증설이나 알고리즘 개선이 정공법입니다.
파이프라인과 슈퍼스칼라 — 코어 안의 조립 라인 #
IPC가 왜 출렁이는지 보려면 코어 내부를 한 층만 열어 보면 됩니다. 코어는 명령어 하나를 끝내고 다음을 시작하는 방식이 아니라, 명령어 처리를 여러 단계로 쪼개 조립 라인처럼 겹쳐 돌립니다. 이것이 파이프라인(pipeline)입니다. 여기에 그 라인을 여러 개 두고 사이클마다 명령어 여러 개를 동시에 밀어 넣는 구조가 슈퍼스칼라(superscalar)입니다.
조립 라인의 약점은 멈춤입니다. 다음 명령어에 필요한 데이터가 아직 안 왔거나, 어느 분기로 갈지 몰라 라인을 잘못 채웠다면, 라인은 비거나 통째로 비워집니다. 이 멈춤(스톨)이 쌓인 결과가 낮은 IPC이고, 멈춤의 두 큰 원인이 다음 두 절의 주제인 캐시 미스와 분기 예측 실패입니다.
캐시 계층 — 미스 한 번의 사이클 비용 #
기초 2편에서 캐시가 “CPU 옆의 작고 빠른 메모리"라는 개념을 잡았습니다. 고급에서 필요한 것은 계층별 비용의 감각입니다. 수치는 세대마다 다르지만 자릿수의 감은 이렇습니다.
| 데이터 위치 | 대략적 지연 | 감각 |
|---|---|---|
| L1 캐시 | 약 4사이클 | 코어 바로 옆, 거의 즉시 |
| L2 캐시 | 약 12사이클 | L1보다 3배 느림 |
| L3 캐시 | 약 40사이클 | L1보다 10배 느림 |
| DRAM | 약 200사이클 이상 | L1보다 50배 이상 느림 |
핵심은 L1과 DRAM 사이의 격차가 50배라는 점입니다. DRAM까지 가는 미스 한 번은 명령어 수백 개를 처리할 수 있었던 시간을 태웁니다. 미스가 자주 나는 코드라면 코어는 일하는 시간보다 기다리는 시간이 길어지고, IPC는 1.0 아래로 가라앉습니다. 큰 배열을 무작위 순서로 헤집는 접근, 포인터를 따라 메모리 곳곳을 점프하는 자료 구조가 전형적인 원인입니다.
분기 예측 — 틀리면 라인을 비웁니다 #
조건문 앞에서 코어는 기다리지 않습니다. 분기 예측기(branch predictor)가 과거 패턴으로 “이쪽으로 갈 것"이라고 찍고, 그 방향의 명령어로 파이프라인을 미리 채웁니다. 예측이 맞으면 공짜지만, 틀리면 잘못 채운 라인을 통째로 비우고 다시 채워야 합니다. 이 비용이 한 번에 대략 15〜20사이클입니다.
현대 예측기는 규칙적인 패턴을 거의 다 맞히므로, 분기 미스 비율은 보통 1% 안쪽입니다. 정렬되지 않은 데이터에 대한 조건 분기, 예측 불가능한 입력에 좌우되는 코드에서 이 비율이 수 퍼센트로 뛰고, 그만큼 IPC를 깎아 먹습니다.
perf stat — 사용률 뒤의 숫자를 꺼내기 #
CPU에는 이런 사건들을 세는 하드웨어 카운터(PMU)가 내장돼 있고, perf stat은 그 값을 읽어 줍니다.
$ perf stat -p 4321 -- sleep 10
Performance counter stats for process id '4321':
39,812.43 msec task-clock # 3.981 CPUs utilized
98,234,567,890 cycles # 2.468 GHz
49,876,543,210 instructions # 0.51 insn per cycle
8,123,456,789 branches # 204.043 M/sec
123,456,789 branch-misses # 1.52% of all branches
2,345,678,901 cache-references
987,654,321 cache-misses # 42.10% of all cache refs
10.001234567 seconds time elapsed읽는 순서는 세 줄입니다.
- insn per cycle — IPC입니다. 위 출력의 0.51은 코어가 사이클의 대부분을 대기에 쓰고 있다는 뜻입니다.
- cache-misses 비율 — 42%면 메모리 접근 열에 넷 이상이 캐시를 뚫고 내려갔다는 뜻이라, 낮은 IPC의 범인으로 캐시 미스를 지목할 수 있습니다.
- branch-misses 비율 — 1.52%는 평범한 수준입니다. 이 사례에서 분기는 무죄입니다.
한 가지 주의가 필요합니다. cycles를 시간으로 나눈 실효 클럭(위 출력의 2.468GHz)이 기본 클럭보다 낮다면, 중급 2편에서 본 스로틀링이나 거버너 문제가 겹쳐 있을 수 있습니다. 마이크로아키텍처 해석은 클럭이 정상이라는 전제 위에서 의미가 있으므로, 실효 클럭 확인을 먼저 합니다.
perf record와 플레임그래프 — 어디서 그러는지 #
perf stat이 “이 프로세스의 병목은 메모리 대기"라는 성격을 알려 준다면, 코드의 어느 부분이 그러는지는 perf record가 답합니다. 주기적으로 실행 중인 함수와 콜 스택을 샘플링해 두고, perf report로 어떤 함수가 사이클을 가장 많이 먹었는지 집계하는 방식입니다.
이 샘플을 한 장의 그림으로 펼친 것이 플레임그래프(flame graph)입니다. 가로 폭이 그 함수(와 그 아래 호출들)가 차지한 시간 비중이고, 세로가 콜 스택의 깊이입니다. 넓은 봉우리를 찾으면 그곳이 사이클을 태우는 코드입니다. perf stat으로 병목의 성격을 잡고, 플레임그래프로 위치를 잡는 순서가 실전의 한 사이클입니다.
사례 — IPC 0.5와 2.0의 다른 처방 #
같은 사용률 100%인 두 서버를 놓고 해석을 끝까지 가 보겠습니다.
- 서버 A: IPC 0.5, cache-misses 40% — 코어는 바쁜 척하지만 실제로는 DRAM 왕복을 기다리는 시간이 대부분입니다. 코어 증설은 기다리는 코어만 늘립니다. 처방은 메모리 접근 쪽입니다. 데이터 구조의 지역성 개선, 접근 순서 정리, 그리고 3편에서 다룰 hugepages와 메모리 대역폭 점검이 후보입니다.
- 서버 B: IPC 2.0, cache-misses 3% — 코어가 연산으로 가득 차 있습니다. 이 100%는 줄여 줄 하드웨어 트릭이 없는 진짜 포화라서, 처방은 코어 증설, 알고리즘 개선, 또는 작업 분산입니다.
중급까지의 지표로는 두 서버가 구분되지 않습니다. 사용률도, 로드도, 클럭도 같을 수 있습니다. IPC와 미스 비율이 비로소 둘을 갈라 주고, 잘못된 처방(서버 A에 코어 증설)에 쓸 돈을 막아 줍니다.
정리 #
이번 글에서 잡은 그림입니다.
- 사용률은 코어 점유 시간일 뿐, 일의 양이 아닙니다. 일의 밀도는 IPC가 보여 줍니다.
- 낮은 IPC의 두 큰 원인은 캐시 미스(DRAM 왕복은 200사이클 이상)와 분기 예측 실패(한 번에 15〜20사이클)입니다.
perf stat에서는 IPC, 캐시 미스 비율, 분기 미스 비율 세 줄을 읽고, 실효 클럭이 정상인지 먼저 확인합니다.perf record와 플레임그래프가 병목의 위치를,perf stat이 병목의 성격을 알려 줍니다.- 같은 100%라도 IPC 0.5는 메모리 처방, 2.0은 연산 처방으로 갈립니다.
다음 — eBPF 관측 #
다음 글인 “하드웨어 고급 #2 eBPF 관측"에서는 관측의 대상을 CPU 내부에서 커널 전체로 넓힙니다. perf가 하드웨어 카운터를 읽었다면, eBPF는 커널 안에 작은 프로그램을 심어 시스템 콜, 디스크 I/O 지연, 네트워크 경로를 실행 중에 들여다봅니다. 재시작 없이 운영 중인 서버를 해부하는 방법입니다.