성능 최적화 (Performance Optimization)
Linux 커널 성능 최적화를 위한 계측 중심 실전 가이드입니다. perf(annotate/c2c/diff/bench), PMU(PEBS/IBS), ftrace/tracepoints, Flame Graph, io_uring 분석을 조합해 CPU·메모리·I/O 병목을 찾아내고, 스케줄러/락 경합/캐시 미스/NUMA 불균형을 정량화하며, 벤치마크 설계부터 회귀 검증까지 포함한 재현 가능한 튜닝 절차를 단계적으로 다룹니다.
핵심 요약
- 전제 결합 — 보안, 성능, 아키텍처 지식을 함께 적용합니다.
- 경계 명확화 — API 경계와 ABI 영향 범위를 먼저 확인합니다.
- 위험 관리 — UAF, race, side-effect 가능성을 우선 점검합니다.
- 계측 기반 판단 — 추측 대신 데이터로 개선 여부를 판단합니다.
- 점진 적용 — 실험 범위를 작게 시작해 단계적으로 확장합니다.
단계별 이해
- 가설 수립
문제와 개선 목표를 수치로 정의합니다. - 제약 분석
호환성, 안정성, 보안 제약을 먼저 확인합니다. - 실험 적용
최소 변경으로 효과와 부작용을 측정합니다. - 정식 반영
검증된 변경만 문서화해 반영합니다.
성능 최적화 개요
커널 성능 최적화는 병목 지점을 정확히 파악하는 것에서 시작합니다. Linux는 perf, ftrace, BPF 등 강력한 프로파일링/추적 도구를 기본 제공하며, 이 도구들을 조합한 체계적 워크플로가 필요합니다.
성능 모니터링 원리 (PMU)
커널 성능 분석의 기반은 CPU에 내장된 PMU(Performance Monitoring Unit)입니다. PMU는 하드웨어 수준에서 CPU 이벤트를 카운팅하는 전용 회로로, 소프트웨어 오버헤드 없이 정밀한 성능 데이터를 수집합니다.
PMU 동작 메커니즘
| 구성 요소 | 역할 | 예시 |
|---|---|---|
| 성능 카운터 레지스터 | 특정 이벤트 발생 횟수를 카운팅 | x86: PMC0~PMCn (일반적으로 4~8개) |
| 이벤트 선택 레지스터 | 카운팅할 이벤트 종류를 지정 | cycles, instructions, cache-misses, branch-misses |
| 오버플로 인터럽트 | 카운터가 지정값에 도달하면 인터럽트 발생 | PMI(Performance Monitoring Interrupt) |
샘플링 vs 카운팅
perf는 두 가지 방식으로 PMU를 활용합니다:
- 카운팅 모드 (
perf stat): 이벤트 발생 횟수를 정확히 세어 총계를 보여줍니다. 오버헤드가 거의 없으나, 어디서 발생했는지는 알 수 없습니다. - 샘플링 모드 (
perf record): 카운터가 N번 이벤트마다 인터럽트를 발생시키고, 그 시점의 IP(Instruction Pointer)와 콜스택을 기록합니다. 이 샘플들의 통계적 분포로 핫 코드 경로를 식별합니다. 샘플링 주파수(예: -F 99 = 초당 99회)가 높을수록 정밀하지만 오버헤드도 증가합니다.
이벤트 멀티플렉싱: 하드웨어 PMC 수(보통 4~8개)보다 많은 이벤트를 동시에 모니터링하면, 커널이 시분할로 PMC를 전환합니다. 이 경우 결과에 추정값(<not counted> 또는 스케일링 계수)이 포함될 수 있습니다.
프로파일링 방법론
체계적인 성능 분석 워크플로는 다음 순서를 따릅니다:
- 전체 지표 수집 (
perf stat): IPC(Instructions Per Cycle), 캐시 미스율, 분기 예측 실패율 등 시스템 전체의 성능 특성을 파악합니다. IPC < 1이면 메모리/I/O 바운드, IPC > 2이면 컴퓨트 바운드 가능성이 높습니다. - 핫스팟 식별 (
perf record+ Flame Graph): CPU 시간 소모가 집중되는 함수와 콜 체인을 식별합니다. - 정밀 분석 (
perf annotate,ftrace): 핫스팟 함수의 명령어별 사이클 분포, 함수 호출 흐름, 지연 원인을 파악합니다. - 가설 검증 및 최적화: 원인에 맞는 최적화를 적용하고 다시 측정하여 효과를 검증합니다.
USE 방법론
Brendan Gregg의 USE(Utilization, Saturation, Errors) 방법론은 모든 하드웨어 리소스에 대해 세 가지 지표를 체계적으로 점검하여 병목을 빠르게 좁힙니다.
TSA 방법론 (Thread State Analysis)
워크로드의 스레드가 어떤 상태에서 시간을 소비하는지를 분석합니다. On-CPU(실행 중), Runnable(대기 중), Sleep(블로킹) 시간의 분포를 보면 병목 유형이 즉시 드러납니다.
| 스레드 상태 | 의미 | 진단 도구 | 최적화 방향 |
|---|---|---|---|
| On-CPU | CPU에서 실행 중 | perf record (On-CPU Flame Graph) | 알고리즘, 컴파일러 최적화, SIMD |
| Runnable | 실행 가능하나 CPU 대기 | runqlat, perf sched latency | CPU 추가, 친화도, 부하 분산 |
| Sleep (I/O) | I/O 완료 대기 | offcputime, biolatency | I/O 최적화, 캐싱, 비동기 I/O |
| Sleep (Lock) | 잠금 획득 대기 | perf lock, offcputime | 잠금 세분화, RCU, lockless |
| Sleep (Network) | 네트워크 응답 대기 | tcplife, offcputime | 연결 풀, 비동기, 배치 처리 |
| Idle | 작업 없음 | mpstat (%idle) | 더 많은 작업 할당, 병렬화 |
방법론 선택: USE 방법론은 "어떤 리소스에 문제가 있나"를 빠르게 좁히고, TSA는 "스레드가 왜 느린가"를 분석합니다. 실전에서는 USE로 리소스 병목을 먼저 파악한 후, 해당 리소스에서 TSA/Flame Graph로 근본 원인을 추적합니다.
perf 종합 가이드
perf는 Linux 커널에 내장된 성능 분석 프레임워크로, perf_event_open(2) 시스템 콜을 통해 PMU 하드웨어 카운터, 소프트웨어 이벤트, tracepoint, kprobe/uprobe 등을 통합적으로 활용합니다. 위의 PMU 원리 섹션에서 설명한 카운팅/샘플링 모드를 실전에서 사용하는 도구입니다.
설정 및 권한
perf를 사용하려면 커널 설정과 시스템 권한이 필요합니다:
# 필수 커널 설정
CONFIG_PERF_EVENTS=y # perf_event 서브시스템
CONFIG_HW_PERF_EVENTS=y # 하드웨어 PMU 지원
CONFIG_DEBUG_INFO=y # 심볼/소스 매핑 (annotate, probe)
CONFIG_DEBUG_INFO_BTF=y # BPF 타입 정보 (선택)
CONFIG_KALLSYMS_ALL=y # 모든 커널 심볼 (권장)
# 설치 (배포판별)
sudo apt install linux-tools-$(uname -r) # Debian/Ubuntu
sudo dnf install perf # Fedora/RHEL
perf_event_paranoid 값 | 허용 범위 | 설명 |
|---|---|---|
-1 | 모든 이벤트, 모든 사용자 | 제한 없음 (개발/테스트 환경) |
0 | 커널+사용자 공간 | CPU 이벤트 허용, raw tracepoint 제한 |
1 | 사용자 공간만 | 커널 프로파일링에 root 또는 CAP_PERFMON 필요 (배포판 기본값은 상이) |
2 | 사용자 공간 (CPU 제한) | 시스템 전체 모니터링 불가 |
3 | 거부 | perf_event_open 완전 차단 |
# 현재 설정 확인 및 변경
cat /proc/sys/kernel/perf_event_paranoid
sudo sysctl kernel.perf_event_paranoid=-1 # 임시 해제
# Linux 5.8+: CAP_PERFMON capability (root 대신 사용)
sudo setcap cap_perfmon+ep /usr/bin/perf
perf list: 이벤트 탐색
perf가 지원하는 이벤트를 확인합니다. 이벤트는 카테고리별로 분류됩니다:
| 카테고리 | 예시 | 소스 |
|---|---|---|
| Hardware | cycles, instructions, cache-misses, branch-misses | PMU 카운터 |
| Software | context-switches, page-faults, cpu-migrations | 커널 소프트웨어 카운터 |
| Cache | L1-dcache-load-misses, LLC-load-misses | PMU 캐시 이벤트 |
| Tracepoint | sched:sched_switch, block:block_rq_issue | 커널 tracepoint |
| PMU | cpu/event=0xc0,umask=0x01/ | 플랫폼 특정 PMU |
| Raw | rNNN (예: r003c = unhalted cycles) | 하드웨어 인코딩 직접 지정 |
# 전체 이벤트 목록
perf list
# 카테고리별 필터
perf list hw # 하드웨어 이벤트만
perf list sw # 소프트웨어 이벤트만
perf list cache # 캐시 이벤트
perf list tracepoint # tracepoint 이벤트
perf list pmu # PMU 특정 이벤트
# 키워드 검색
perf list | grep -i 'branch'
# Raw 이벤트: Intel SDM/AMD APM에서 인코딩 확인
# rNNN 형식: r + EventSelect(8bit) + UnitMask(8bit)
perf stat -e r003c ./program # CPU_CLK_UNHALTED.CORE
perf stat: 이벤트 카운팅 심화
perf stat은 카운팅 모드로 이벤트 총계를 수집합니다. 기본 사용법은 디버깅 페이지를 참조하고, 여기서는 분석적 해석에 집중합니다.
# 상세 카운터 (-d: L1, -dd: L1+LLC, -ddd: L1+LLC+TLB+분기)
perf stat -ddd ./program
# 반복 측정으로 통계적 신뢰도 확보
perf stat -r 5 -e cycles,instructions ./program
# 출력: 평균 ± 표준편차 (stddev)
# 인터벌 모드: 1초 간격으로 카운터 출력 (추세 분석)
perf stat -I 1000 -e cycles,instructions -a
# 이벤트 그룹: 동일 PMC 타임슬라이스에서 측정 (비율 정확도 향상)
perf stat -e '{cycles,instructions}' ./program
# CPU별 통계
perf stat -e cycles -A -a -- sleep 5
IPC 해석 가이드:
- IPC < 1.0: 메모리/I/O 바운드 가능성 — 캐시 미스, TLB 미스, 메모리 대역폭 포화 확인
- IPC 1.0~2.0: 혼합 워크로드 — 분기 예측 실패, 파이프라인 스톨 확인
- IPC > 2.0: 컴퓨트 바운드 — 알고리즘/SIMD 최적화가 효과적
cache-misses / cache-references 비율이 5% 이상이면 캐시 최적화가 필요합니다.
perf record / report: 샘플링 프로파일링
perf record는 샘플링 모드로 프로파일 데이터를 perf.data 파일에 기록하고, perf report로 분석합니다.
# 샘플링 주파수 vs 주기
perf record -F 99 ./program # -F: 초당 99 샘플 (주파수)
perf record -c 100000 ./program # -c: 100000 이벤트마다 1 샘플 (주기)
# -F 99: 커널이 주파수를 유지하도록 주기를 자동 조절 (권장)
# -c: 정확한 이벤트 수 기준 (이벤트 빈도가 변할 때 유용)
# 콜 그래프 포함 기록 (다음 섹션에서 방법 비교)
perf record -g -F 99 -p $(pidof myapp) -- sleep 30
# 시스템 전체 + 커널 심볼
perf record -ag -F 99 -- sleep 10
# report 주요 옵션
perf report # TUI 모드 (대화식)
perf report --stdio # 텍스트 출력 (스크립트/로그용)
perf report --hierarchy # 콜 체인 계층 구조
perf report --sort=dso,sym # 라이브러리/심볼별 정렬
perf report --percent-limit=1 # 1% 미만 항목 필터링
콜 그래프 수집 방법 비교
perf record -g로 콜 그래프(콜 스택)를 수집할 때 세 가지 방법을 선택할 수 있으며, 각각 트레이드오프가 다릅니다:
| 방법 | 옵션 | 오버헤드 | 정확도 | 스택 깊이 | 요구사항 |
|---|---|---|---|---|---|
| Frame Pointer (fp) | --call-graph fp | 최소 | 중간 | 무제한 | -fno-omit-frame-pointer 컴파일 |
| DWARF | --call-graph dwarf | 높음 | 높음 | 무제한 | CONFIG_DEBUG_INFO, 디버그 심볼 |
| LBR | --call-graph lbr | 최소 | 높음 | 8~32단계 | Intel CPU (Haswell+), CONFIG_PERF_EVENTS_INTEL_LBR |
# Frame Pointer: 빠르지만 -fno-omit-frame-pointer로 빌드된 코드 필요
perf record --call-graph fp -F 99 ./program
# DWARF: 가장 정확하지만 perf.data 크기 큼 (스택 덤프 저장)
perf record --call-graph dwarf,32768 -F 99 ./program
# 32768 = 스택 덤프 크기 (기본 8192, 깊은 스택이면 증가)
# LBR: Intel CPU에서 하드웨어 지원, 낮은 오버헤드 + 높은 정확도
perf record --call-graph lbr -F 99 ./program
콜 그래프 방법 선택 가이드: 커널 프로파일링은 fp가 기본(커널은 frame pointer 사용). 사용자 공간은 dwarf가 가장 정확하며, Intel 환경에서 오버헤드가 문제면 lbr을 선택하세요. -g만 쓰면 기본값은 fp입니다.
perf annotate: 명령어 수준 분석
perf annotate는 핫스팟 함수의 어셈블리/소스 코드에 샘플 분포를 매핑하여, 어떤 명령어가 가장 많은 사이클을 소모하는지 보여줍니다.
# 기본 사용법 (perf record 후)
perf record -g -F 99 ./program
perf annotate # TUI: 심볼 선택 → 명령어별 비율
perf annotate --symbol=hot_function # 특정 함수 직접 지정
# 소스 코드 인터리빙 (CONFIG_DEBUG_INFO 필요)
perf annotate --source # 소스 + 어셈블리 동시 표시
perf annotate --no-source # 어셈블리만 표시
# stdio 출력 (스크립트/분석용)
perf annotate --stdio --symbol=copy_page
annotate 출력 예시 — 각 어셈블리 명령어 앞에 해당 명령어의 샘플 비율(%)이 표시됩니다:
│ copy_page():
0.50 │ mov (%rsi),%rax
45.20 │ mov %rax,(%rdi) ← 캐시 미스로 스톨 (핫 명령어)
0.30 │ mov 0x8(%rsi),%rax
2.10 │ mov %rax,0x8(%rdi)
│ ...
핫 명령어 해석: mov 명령어에 높은 비율이 나타나면 대개 캐시 미스(메모리 스톨)가 원인입니다. 이때 perf stat -e L1-dcache-load-misses와 함께 확인하세요. div/idiv에 높은 비율이면 정수 나눗셈 비용, 분기 명령에 높으면 분기 예측 실패를 의심합니다.
perf top: 실시간 모니터링
perf top은 top 명령어처럼 실시간으로 CPU를 가장 많이 소모하는 함수를 보여줍니다.
# 시스템 전체 실시간 프로파일링
perf top # 기본: cycles 이벤트
perf top -g # 콜 그래프 포함
# 특정 프로세스 / 이벤트 필터링
perf top -p $(pidof myapp) # PID 기반
perf top -e cache-misses # 캐시 미스 기준 정렬
# TUI에서 심볼 선택 후 Enter → annotate 뷰 (명령어별 분석)
# 's' 키: 심볼 필터, 'K': 커널 심볼 토글
perf trace: 시스템 콜 추적
perf trace는 strace와 유사하지만, 커널 tracepoint 기반으로 동작하여 오버헤드가 훨씬 낮습니다.
| 특성 | strace | perf trace |
|---|---|---|
| 메커니즘 | ptrace (프로세스 중단) | perf_event tracepoint |
| 오버헤드 | 높음 (2~10x 느려짐) | 낮음 (<5%) |
| 시스템 전체 | PID별만 가능 | -a로 시스템 전체 |
| 통계 모드 | -c | -s (더 상세) |
| 이벤트 확장 | 시스콜만 | 시스콜 + page-fault + 기타 이벤트 |
# 기본 사용법
perf trace ./program # 새 프로세스 추적
perf trace -p $(pidof myapp) # 실행 중인 프로세스
# 필터링
perf trace -e open,read,write ./program # 특정 시스콜만
perf trace --duration 10 -p 1234 # 10ms 이상 소요된 콜만
# 요약 모드: 시스콜별 횟수/시간 통계
perf trace -s ./program
# 출력: syscall calls errors total(ms) min(ms) avg(ms) max(ms)
# 시스템 전체 + page fault 포함
perf trace -a -e 'major-faults,minor-faults' -- sleep 5
perf c2c: False Sharing 탐지
perf c2c(cache-to-cache)는 HITM(Hit in Modified) 이벤트를 분석하여, 여러 CPU 코어가 동일 캐시라인을 경쟁적으로 수정하는 false sharing을 탐지합니다.
# 1단계: 메모리 접근 샘플 기록
perf c2c record -a -- sleep 10
# 또는 특정 프로세스
perf c2c record -p $(pidof myapp) -- sleep 10
# 2단계: HITM 분석 리포트
perf c2c report --stdio
# Shared Data Cache Line Table에서:
# - Rmt HITM: 원격 소켓에서 수정된 라인 접근 (가장 비쌈)
# - Lcl HITM: 같은 소켓 내 다른 코어에서 수정된 라인 접근
# 3단계: 핫 캐시라인의 오프셋 + 데이터 소스 확인
# → struct 멤버 분리 또는 ____cacheline_aligned 적용
캐시 코히런시 프로토콜(MESI/MOESI)과 false sharing의 원리, ____cacheline_aligned 해결 기법 등 상세 내용은 CPU 캐시 — False Sharing 페이지를 참고하세요.
perf diff: 프로파일 비교
perf diff는 두 개의 perf.data 파일을 비교하여 최적화 전후의 성능 변화를 정량적으로 분석합니다.
# A/B 테스트 워크플로
# 1. 최적화 전 프로파일 기록
perf record -g -F 99 -o baseline.data -- ./program_v1
# 2. 최적화 후 프로파일 기록
perf record -g -F 99 -o optimized.data -- ./program_v2
# 3. 비교 분석
perf diff baseline.data optimized.data
# 출력 컬럼:
# Baseline Delta Symbol
# -------- ----- ------
# 15.30% -8.20% hot_function ← 8.2% 감소 (개선)
# 3.10% +2.50% new_function ← 2.5% 증가 (회귀)
# delta 기준 정렬
perf diff --sort=delta baseline.data optimized.data
perf bench: 내장 벤치마크
perf bench는 커널/하드웨어 서브시스템의 기본 성능을 측정하는 마이크로벤치마크 모음입니다. 시스템 설정 변경이나 커널 업그레이드 전후 비교에 유용합니다.
| 카테고리 | 벤치마크 | 측정 대상 |
|---|---|---|
sched | messaging, pipe | 스케줄러 컨텍스트 스위치, IPC 성능 |
mem | memcpy, memset | 메모리 대역폭 |
numa | mem | NUMA 노드 간 메모리 접근 지연 |
futex | hash, wake, requeue, lock-pi | futex 서브시스템 성능 |
epoll | wait, ctl | epoll 이벤트 처리 성능 |
# 스케줄러: 메시지 패싱 성능 (프로세스/스레드 간)
perf bench sched messaging -g 20 -t -l 1000
# -g 20: 20개 그룹, -t: 스레드 사용, -l 1000: 1000회 반복
# 메모리: memcpy 대역폭
perf bench mem memcpy -s 4GB -l 5
# futex: 잠금 성능 (nthread별)
perf bench futex lock-pi -t 8
# NUMA: 노드 간 메모리 접근
perf bench numa mem -p 4 -t 4 -P 1024 -T 0
# 전체 벤치마크 실행
perf bench all
Intel/AMD 플랫폼별 PMU
범용 이벤트(cycles, instructions) 외에, 플랫폼별 PMU 확장 기능을 활용하면 더 정밀한 분석이 가능합니다.
Intel PEBS (Precise Event-Based Sampling)
PEBS는 이벤트 발생 시점의 정확한 IP(Instruction Pointer)를 하드웨어가 기록합니다. 일반 샘플링의 "skid"(인터럽트 지연으로 인한 IP 부정확) 문제를 해결합니다.
# PEBS 이벤트: 이벤트명 뒤에 :p 또는 :pp 수정자
# :p = precise level 1, :pp = precise level 2 (최대 정밀도)
perf record -e cycles:pp -F 99 ./program
perf record -e mem-loads:pp -F 97 ./program # 메모리 로드 정밀 샘플링
# PEBS + annotate로 정확한 명령어별 분석
perf annotate --symbol=hot_function
AMD IBS (Instruction-Based Sampling)
IBS는 명령어 파이프라인에서 직접 샘플링하여 PEBS와 유사한 정밀도를 제공합니다. Fetch와 Op 두 가지 모드가 있습니다.
# AMD IBS Fetch 샘플링
perf record -e ibs_fetch// -c 100000 ./program
# AMD IBS Op 샘플링 (실행된 마이크로-op 기반)
perf record -e ibs_op// -c 100000 ./program
# IBS + 메모리 접근 분석
perf mem record -- ./program # AMD에서 자동으로 IBS 사용
플랫폼별 유용 이벤트
| 이벤트 | 플랫폼 | 용도 |
|---|---|---|
mem-loads:pp, mem-stores:pp | Intel (PEBS) | 메모리 접근 지연/소스 분석 |
ibs_op//, ibs_fetch// | AMD (IBS) | 명령어/페치 수준 정밀 분석 |
topdown-* | Intel (Skylake+) | Top-Down 마이크로아키텍처 분석 |
offcore_response | Intel | L3 미스 후 메모리 응답 소스 분석 |
cpu/event=0x...,umask=0x.../ | 공통 | 벤더 문서의 raw 이벤트 직접 지정 |
MSR 레지스터를 통한 PMU 제어, HWP/RAPL 등 하드웨어 성능 모니터링의 저수준 세부사항은 MSR 레지스터 페이지를 참고하세요.
Flame Graph 분석
Flame Graph는 CPU 프로파일 데이터를 스택 깊이별로 시각화하여 핫 코드 경로를 직관적으로 식별합니다:
# On-CPU Flame Graph
perf record -F 99 -ag -- sleep 30
perf script > out.perf
stackcollapse-perf.pl out.perf > out.folded
flamegraph.pl out.folded > cpu_flame.svg
# Off-CPU Flame Graph (블로킹 시간 분석)
# BPF 기반 (bcc tools)
offcputime-bpfcc -df -p $(pidof myapp) 30 > offcpu.folded
flamegraph.pl --color=io offcpu.folded > offcpu_flame.svg
# Memory allocation Flame Graph
perf record -e kmem:kmalloc -ag -- sleep 10
perf script | stackcollapse-perf.pl | flamegraph.pl > mem_flame.svg
Differential Flame Graph
perf diff의 결과를 시각화하여 최적화 전후 변화를 색상으로 표현합니다:
# 최적화 전/후 프로파일 수집
perf record -F 99 -ag -o before.data -- ./workload_v1
perf record -F 99 -ag -o after.data -- ./workload_v2
# Differential Flame Graph 생성
perf script -i before.data | stackcollapse-perf.pl > before.folded
perf script -i after.data | stackcollapse-perf.pl > after.folded
difffolded.pl before.folded after.folded | flamegraph.pl > diff_flame.svg
# 빨간색: 증가 (회귀), 파란색: 감소 (개선)
Intel® PCM (Performance Counter Monitor)
Intel PCM (Performance Counter Monitor)은 별도 페이지로 분리되었습니다. → Intel PCM 완전 가이드: pcm-memory, pcm-tpmi, CXL 모니터링, Grafana 대시보드, TMA 분석, Emerald/Granite Rapids, 실전 진단 시나리오 6개
Intel PCM은 Intel이 개발한 오픈소스 성능 모니터링 도구 모음으로, CPU 코어 성능 카운터, 메모리 대역폭, QPI/UPI 링크 사용률, PCIe 대역폭, 전력 소비량 등을 실시간으로 측정합니다. perf가 커널에 내장된 범용 프로파일링 프레임워크라면, PCM은 Intel 플랫폼에 특화된 시스템 수준 하드웨어 모니터링 도구입니다.
PCM vs perf: perf는 커널 perf_event 서브시스템을 통해 PMU에 접근하며 프로세스/함수 수준 프로파일링에 강합니다. 반면 PCM은 MSR/PCI config 레지스터에 직접 접근하여 언코어(Uncore) 카운터 — 메모리 컨트롤러, QPI/UPI 링크, PCIe, 전력 등 코어 외부 리소스 — 를 포함한 시스템 전체 관점의 모니터링에 특화되어 있습니다.
PCM 아키텍처 개요
PCM은 세 가지 경로로 하드웨어 카운터에 접근합니다:
| 접근 경로 | 대상 카운터 | 커널 요구사항 | 용도 |
|---|---|---|---|
| MSR 드라이버 | Core PMU, Fixed CTR, RAPL | msr 커널 모듈 | 코어별 성능 카운터, 전력 측정 |
| perf_event | Core/Uncore PMU | CONFIG_PERF_EVENTS | perf 서브시스템 경유 (권한 제어 용이) |
| sysfs/MMIO | TPMI, PCI config space | TPMI 드라이버 (6.3+) | 언코어 카운터, PCIe/CXL 모니터링 |
설치 및 빌드
# 소스에서 빌드 (GitHub)
git clone --recursive https://github.com/intel/pcm.git
cd pcm
mkdir build && cd build
cmake ..
cmake --build . --parallel
# 패키지 매니저로 설치 (배포판별)
sudo apt install pcm # Ubuntu 22.04+ / Debian
sudo dnf install pcm # Fedora / RHEL 9+
# Docker로 실행 (권한 필요)
docker run --privileged -d \
--name pcm --ipc=host --pid=host \
-v /sys:/sys:rw \
ghcr.io/intel/pcm
커널 모듈 및 접근 권한 설정
PCM은 MSR 레지스터에 직접 접근하므로 msr 커널 모듈과 root 권한(또는 CAP_SYS_RAWIO)이 필요합니다:
# msr 커널 모듈 로드
sudo modprobe msr
# NMI watchdog 비활성화 (PMU 카운터 점유 방지)
# NMI watchdog가 고정 카운터를 사용하면 PCM과 충돌
sudo sysctl kernel.nmi_watchdog=0
# Secure Boot 환경에서 MSR 접근 허용
# /dev/cpu/*/msr 접근이 차단될 경우:
sudo modprobe msr allow_writes=on
# perf_event 기반 접근 사용 시 (MSR 직접 접근 대신)
# PCM은 perf_event 모드도 지원 (-e 옵션 또는 환경변수)
export PCM_USE_PERF=1
sudo sysctl kernel.perf_event_paranoid=-1
NMI watchdog 충돌: NMI watchdog는 고정 카운터(INST_RETIRED.ANY)를 점유합니다. PCM 실행 전 nmi_watchdog=0으로 비활성화하지 않으면 카운터 프로그래밍이 실패하거나 부정확한 결과가 나올 수 있습니다. PCM은 시작 시 이 충돌을 자동 감지하고 경고합니다.
핵심 도구 모음
PCM은 목적별로 분리된 여러 도구를 제공합니다. 각 도구는 Intel CPU의 특정 하드웨어 카운터에 최적화되어 있습니다.
pcm: 코어 성능 카운터
기본 도구로, 코어별 IPC(Instructions Per Cycle), L2/L3 캐시 히트율, 분기 예측률 등 핵심 지표를 실시간 표시합니다.
# 기본 실행: 1초 간격으로 코어별 성능 카운터 표시
sudo pcm
# 측정 간격 및 반복 횟수 지정
sudo pcm 0.5 -i=20 # 0.5초 간격, 20회 측정
# CSV 출력 (후처리/시각화용)
sudo pcm 1 -csv=result.csv
# 특정 프로그램 실행 동안 측정
sudo pcm -- ./benchmark
# 코어 이벤트 커스텀 지정 (최대 4개 범용 카운터)
sudo pcm -e core/config=0x2e,config1=0x41,name=LLC_MISSES/
pcm 출력의 주요 지표:
| 지표 | 설명 | 정상 범위 | 이상 징후 |
|---|---|---|---|
| IPC | Instructions Per Cycle | 1.0~4.0 | < 0.5이면 심각한 메모리 스톨 |
| L2 Hit Ratio | L2 캐시 히트율 | > 95% | < 80%이면 작업 셋 > L2 |
| L3 Hit Ratio | L3 (LLC) 캐시 히트율 | > 80% | < 50%이면 메모리 대역폭 확인 필요 |
| FREQ | 실제 동작 주파수 (GHz) | 부스트 근처 | 기본 클럭 이하이면 전력/열 제한 |
| TEMP | 코어 온도 (°C) | < TjMax-10 | TjMax 도달 시 스로틀링 |
| INST | 은퇴된 명령어 수 | 워크로드 의존 | 코어 간 불균형 → 부하 분산 문제 |
| AFREQ | Active Frequency (활성 시 주파수) | ≈ Max Turbo | 기본 클럭 근처이면 전력 제한 |
| L3MISS | L3 캐시 미스 횟수 | 워크로드 의존 | 높으면 메모리 BW 병목 |
pcm-memory: 메모리 대역폭 모니터링
소켓/채널별 메모리 대역폭(Read/Write/Total)을 iMC(Integrated Memory Controller) 카운터로 실시간 측정합니다. NUMA 환경에서 메모리 병목 진단에 매우 중요한 지표입니다.
# 소켓/채널별 메모리 대역폭 측정
sudo pcm-memory
# 0.5초 간격, 파일 출력
sudo pcm-memory 0.5 -csv=mem_bw.csv
# 특정 소켓만 모니터링
sudo pcm-memory -s 0 # 소켓 0만
출력 예시:
| Socket | Read (GB/s) | Write (GB/s) | PMM Read | PMM Write | Total (GB/s) |
|---|---|---|---|---|---|
| 0 | 45.2 | 12.8 | N/A | N/A | 58.0 |
| 1 | 43.8 | 11.5 | N/A | N/A | 55.3 |
| System | 89.0 | 24.3 | N/A | N/A | 113.3 |
메모리 대역폭 포화 판단: 이론적 최대 대역폭은 채널 수 × DDR 전송률 × 8바이트로 계산됩니다. 예: 6채널 DDR5-4800 = 6 × 4800MT/s × 8B ≈ 230 GB/s. 측정값이 이론값의 70% 이상이면 메모리 BW 포화 상태입니다. perf stat -e LLC-load-misses와 함께 확인하세요.
pcm-pcie: PCIe 대역폭 모니터링
IIO(I/O 유닛) 언코어 카운터를 통해 PCIe 포트별 인바운드/아웃바운드 대역폭을 측정합니다. NVMe SSD, GPU, 네트워크 카드 등의 I/O 병목 진단에 사용합니다.
# PCIe 포트별 대역폭 (GB/s)
sudo pcm-pcie
# 이벤트 모드: Part0~Part3 상세 분류
sudo pcm-pcie -e
# 특정 BDF (Bus:Device.Function) 필터링
sudo pcm-pcie -B 0x3a:0x00.0
# CSV 출력
sudo pcm-pcie 1 -csv=pcie_bw.csv
# CXL 메모리 대역폭 모니터링 (지원 플랫폼)
sudo pcm-pcie -cxl
pcm-power: 전력 및 열 모니터링
RAPL(Running Average Power Limit) MSR과 온도 센서를 통해 패키지/DRAM/PP0/PP1별 전력 소비와 코어 온도, C-State 상주 비율을 실시간으로 표시합니다.
# 전력/온도/C-State 모니터링
sudo pcm-power
# 출력 예시:
# Package 0: Consumed energy (Joules): 85.2 | Thermal headroom: 23°C
# Package 0: C2 residency: 45.2% | C6 residency: 32.1%
# DRAM energy: 12.8 J
# 전력 변화 추세 관찰 (CSV)
sudo pcm-power 1 -csv=power.csv
pcm-latency: 메모리 레이턴시 측정
메모리 접근 레이턴시를 측정합니다. NUMA 환경에서 로컬/원격 메모리 접근 지연 차이를 정량적으로 파악할 수 있습니다.
# 메모리 레이턴시 측정
sudo pcm-latency
# 특정 소켓 대상
sudo pcm-latency -s 0
pcm-numa: NUMA 트래픽 분석
NUMA 환경에서 로컬/원격 메모리 접근 비율을 측정하여 메모리 배치 최적화 기회를 식별합니다.
# NUMA 로컬/원격 메모리 접근 비율 측정
sudo pcm-numa
# 출력 예시:
# Socket 0: Local Memory Accesses: 95.2% | Remote: 4.8%
# Socket 1: Local Memory Accesses: 87.3% | Remote: 12.7% ← 최적화 필요
# NUMA 최적화 확인: 원격 접근이 10% 이상이면
# numactl --membind 또는 cpuset으로 바인딩 조정 필요
pcm-iio: I/O 유닛 통계
IIO(I/O 유닛) 스택별 인바운드/아웃바운드 트래픽을 상세 분석합니다. PCIe 디바이스와 CPU 소켓 간 데이터 흐름을 파악합니다.
# I/O 유닛(IIO) 스택별 트래픽
sudo pcm-iio
# CSV 출력
sudo pcm-iio 1 -csv=iio.csv
pcm-raw: Raw 카운터 프로그래밍
사전 정의 도구로 측정할 수 없는 커스텀 이벤트를 JSON 기반 이벤트 정의 파일로 프로그래밍합니다. Intel SDM의 이벤트 코드를 직접 지정할 수 있습니다.
# JSON 이벤트 정의 파일 사용
sudo pcm-raw -e events.json
# 이벤트 정의 예시 (events.json):
# {
# "core": {
# "programmable": [
# { "name": "L2_MISS", "event": "0x2e", "umask": "0x41" },
# { "name": "DTLB_MISS", "event": "0x08", "umask": "0x20" }
# ]
# }
# }
# PCM 배포에 포함된 사전 정의 이벤트
ls /usr/share/pcm/PMURegisterDeclarations/
# → SPR/ ICX/ SKX/ ... (마이크로아키텍처별 이벤트 파일)
pcm-sensor-server: 원격 모니터링 (REST API / Grafana)
pcm-sensor-server는 PCM 데이터를 HTTP/HTTPS REST API로 노출하여, Grafana, Prometheus, Telegraf 등 외부 모니터링 시스템과 통합합니다.
# REST API 서버 시작 (기본 포트: 9738)
sudo pcm-sensor-server
# HTTPS + 포트 지정
sudo pcm-sensor-server -p 19738 --https 443 \
--key server.key --cert server.crt
# JSON 데이터 조회
curl -s http://localhost:9738/metrics | head -20
# Prometheus 형식 메트릭 노출 → Grafana 대시보드 연동
# Grafana 대시보드 활용
# PCM 소스의 grafana/ 디렉터리에 사전 정의 대시보드 JSON 포함
# → Grafana에서 Import하면 코어/메모리/PCIe/전력 대시보드 즉시 사용
pcm-tpmi: Topology Aware Register & PM Interface (6.3+)
Sapphire Rapids 이후 서버 플랫폼에서 TPMI(Topology Aware Register and PM Interface)를 통해 언코어 카운터에 접근합니다. MSR 대신 MMIO 기반으로 동작하여 Secure Boot 환경에서도 사용 가능합니다.
# TPMI 기반 언코어 카운터 접근
sudo pcm-tpmi
# TPMI 지원 확인
ls /sys/bus/auxiliary/devices/ | grep intel_vsec
# intel_vsec 드라이버가 로드되어 있으면 TPMI 지원
실전 활용 시나리오
시나리오 1: 메모리 대역폭 포화 진단
애플리케이션이 CPU 사용률은 높지만 IPC가 낮은 경우, 메모리 대역폭 포화가 원인일 수 있습니다:
# 1단계: IPC 확인 → 메모리 바운드 여부 판단
sudo pcm 1 -i=5
# → IPC < 1.0 + L3 Hit Ratio < 50% → 메모리 바운드 의심
# 2단계: 메모리 대역폭 측정
sudo pcm-memory 0.5
# → Read BW가 이론값의 70% 이상이면 BW 포화
# → 채널 간 불균형이 크면 DIMM 구성 확인
# 3단계: NUMA 배치 확인
sudo pcm-numa
# → Remote Access > 10%이면 메모리 배치 최적화
# → numactl --membind=0 ./app 으로 로컬 바인딩
# 4단계: perf로 핫스팟 함수의 LLC 미스 확인
perf record -e LLC-load-misses:pp -c 10000 -- ./app
perf report
시나리오 2: PCIe I/O 병목 진단
# NVMe/GPU가 예상 성능에 미달할 때
# 1단계: PCIe 대역폭 측정
sudo pcm-pcie 1
# → 디바이스별 인바운드/아웃바운드 BW 확인
# → Gen4 x16: 이론 31.5 GB/s, 실측이 50% 미만이면 문제
# 2단계: IIO 스택별 상세 분석
sudo pcm-iio 1
# → 어느 IIO 스택에서 병목이 발생하는지 파악
# 3단계: 전력 제한 확인 (전력 제한으로 I/O 성능 저하 가능)
sudo pcm-power 1
# → PKG 전력이 TDP 근처이면 전력 스로틀링
시나리오 3: 코어 주파수 스로틀링 진단
# 성능이 간헐적으로 저하될 때 (열/전력 제한 의심)
sudo pcm 0.5 -csv=freq_trace.csv -i=60
# → FREQ/AFREQ 컬럼에서 주파수 변동 추적
# → TEMP가 TjMax에 근접하면 열 스로틀링
# → C6 residency가 높으면 idle 상태 비율 과다
# turbostat 병행 (PCM과 보완적)
sudo turbostat --interval 1 --show Core,CPU,Avg_MHz,Busy%,Bzy_MHz,PkgWatt
# Busy% 100%인데 Bzy_MHz < Max Turbo이면 전력/열 제한
PCM과 대안 도구 비교
| 도구 | 제공자 | 카운터 접근 | 강점 | 제한 |
|---|---|---|---|---|
| Intel PCM | Intel (오픈소스) | MSR, perf_event, MMIO | 언코어 카운터 (iMC, UPI, IIO) 통합, REST API | Intel CPU 전용 |
| perf | 커널 내장 | perf_event | 범용 프로파일링, 프로세스/함수 수준 분석 | 언코어 카운터 설정 복잡 |
| turbostat | 커널 소스 포함 | MSR | 주파수/전력/C-State 특화, 커널과 함께 배포 | 성능 카운터 제한적 |
| pmu-tools (toplev) | Andi Kleen | perf 위에 구축 | Top-Down 마이크로아키텍처 분석 (TMA) 자동화 | 설정 복잡, Intel 전용 |
| likwid | RRZE (독일) | MSR, perf_event | Intel + AMD 지원, 그룹 기반 측정 | 언코어 카운터 PCM보다 제한적 |
Top-Down 마이크로아키텍처 분석 (TMA) 연계
Intel의 Top-Down Microarchitecture Analysis (TMA) 방법론은 CPU 파이프라인의 병목을 4개 대분류(Frontend Bound, Backend Bound, Bad Speculation, Retiring)로 분류합니다. PCM과 pmu-tools/toplev을 결합하면 체계적인 병목 분석이 가능합니다.
# pmu-tools 설치
git clone https://github.com/andikleen/pmu-tools.git
cd pmu-tools
# toplev: TMA Level 1 (대분류) 분석
sudo python3 toplev.py --core S0-C0 -l1 -v -- ./benchmark
# 출력: FE(Frontend) 30%, BE(Backend) 45%, BadSpec 5%, Retiring 20%
# → Backend Bound가 지배적 → 메모리/코어 바운드 분석 필요
# Level 2: Backend Bound 상세 분해
sudo python3 toplev.py --core S0-C0 -l2 -v -- ./benchmark
# → Memory Bound 35%, Core Bound 10%
# Level 3+: Memory Bound 세부 원인
sudo python3 toplev.py --core S0-C0 -l3 --nodes '!+Memory*' -v -- ./benchmark
# → L3 Bound 8%, DRAM Bound 22%, Store Bound 5%
# → pcm-memory로 DRAM BW 포화 확인
# PCM + toplev 병행 워크플로
# 1. pcm: 전체 시스템 IPC/캐시/주파수 개요 → 이상 징후 파악
# 2. toplev -l1~l3: 병목 카테고리 좁히기
# 3. pcm-memory/pcm-pcie: 구체적 하드웨어 병목 수치 확인
# 4. perf record + annotate: 함수/명령어 수준 최적화
커널 드라이버 및 관련 소스
PCM이 내부적으로 사용하는 커널 인프라:
| 커널 드라이버/인터페이스 | 경로 | 역할 |
|---|---|---|
msr 모듈 | arch/x86/kernel/msr.c | /dev/cpu/N/msr 문자 장치 제공 |
intel_uncore PMU | arch/x86/events/intel/uncore*.c | perf 서브시스템에 언코어 PMU 등록 |
intel_rapl | drivers/powercap/intel_rapl_*.c | RAPL 전력 측정 (powercap 프레임워크) |
intel_vsec/tpmi | drivers/platform/x86/intel/tpmi.c | TPMI 레지스터 MMIO 접근 (6.3+) |
intel-cstate PMU | arch/x86/events/intel/cstate.c | C-State 상주 시간 카운터 |
| PCI config space | /sys/bus/pci/devices/*/config | 언코어 PCI 레지스터 접근 (iMC, UPI) |
/* PCM이 MSR을 읽는 커널 경로 (arch/x86/kernel/msr.c) */
static ssize_t msr_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
u32 __user *tmp = (u32 __user *)buf;
u32 data[2];
u32 reg = *ppos; /* MSR 주소 = file offset */
int cpu = iminor(file->f_path.dentry->d_inode);
int err;
/* 해당 CPU에서 RDMSR 실행 */
err = rdmsr_safe_on_cpu(cpu, reg, &data[0], &data[1]);
if (err)
return -EIO;
if (copy_to_user(tmp, &data, 8))
return -EFAULT;
return 8;
}
/* PCM의 실제 MSR 읽기 (user space → /dev/cpu/N/msr) */
/* pread(fd, &data, sizeof(data), MSR_ADDR) */
/* 예: pread(fd, &val, 8, 0x38F) → IA32_PERF_GLOBAL_CTRL */
# PCM에 필요한 커널 설정
CONFIG_X86_MSR=m # /dev/cpu/*/msr (필수)
CONFIG_PERF_EVENTS=y # perf_event 모드 사용 시
CONFIG_INTEL_UNCORE=m # 언코어 PMU (perf_event 경유 시)
CONFIG_INTEL_RAPL=m # RAPL 전력 측정
CONFIG_INTEL_VSEC=m # TPMI 지원 (SPR+ 서버)
CONFIG_INTEL_PMT_TELEMETRY=m # PMT 텔레메트리
# msr 모듈 자동 로드 설정
echo "msr" | sudo tee /etc/modules-load.d/msr.conf
perf_event 모드 vs MSR 직접 접근: PCM_USE_PERF=1 환경 변수를 설정하면 PCM이 MSR 직접 접근 대신 perf_event_open()을 통해 카운터에 접근합니다. 이 모드는 msr 커널 모듈이 불필요하고, perf_event_paranoid로 권한을 세밀하게 제어할 수 있습니다. 다만 일부 언코어 카운터는 perf_event로 접근이 불가하므로 MSR 모드가 더 완전한 데이터를 제공합니다.
ftrace 성능 분석 활용
ftrace는 커널 내장 추적 프레임워크입니다. 핵심 원리는 동적 계측(dynamic instrumentation)으로, 컴파일 시 함수 엔트리 계측 지점(mcount/fentry)이 준비되고, 런타임에 동적 패치를 통해 NOP/호출 경로를 전환합니다. 비활성 시 오버헤드는 거의 0입니다. 상세 원리는 ftrace 전용 페이지를 참조하세요. 여기서는 성능 분석에 특화된 활용법에 집중합니다.
# ftrace 디렉터리
cd /sys/kernel/tracing # 또는 /sys/kernel/debug/tracing
# ── 함수 지연 시간 측정 (function_graph) ──
echo function_graph > current_tracer
echo vfs_read > set_graph_function
echo 1 > tracing_on
cat trace
# 1) | vfs_read() {
# 1) 0.523 us | rw_verify_area();
# 1) | __vfs_read() {
# 1) 0.891 us | new_sync_read();
# 1) 1.234 us | }
# 1) 2.345 us | } ← 전체 실행 시간
echo 0 > tracing_on
# ── trace-cmd: ftrace의 사용자 친화적 래퍼 ──
# 설치: apt install trace-cmd
trace-cmd record -p function_graph -g vfs_read -P $(pidof myapp)
trace-cmd report | head -50
# ── 히스토그램 트리거 (6.1+): ftrace 기반 지연 분포 ──
# sched_switch 이벤트의 지연 히스토그램
echo 'hist:keys=next_pid:vals=hitcount:sort=hitcount.descending:size=32' \
> events/sched/sched_switch/trigger
cat events/sched/sched_switch/hist
# ── 함수 추적 + 필터 ──
echo function > current_tracer
echo do_sys_openat2 > set_ftrace_filter # 특정 함수만
echo '*lock*' > set_ftrace_filter # 와일드카드 패턴
echo '!*debug*' >> set_ftrace_filter # 패턴 제외
# ── 이벤트 추적 (tracepoint) ──
echo 1 > events/sched/sched_switch/enable
echo 1 > events/block/block_rq_issue/enable
echo 1 > tracing_on
cat trace_pipe # 실시간 스트리밍
# ── 지연 추적기: 최대 지연 원인 찾기 ──
echo irqsoff > current_tracer # IRQ 비활성화 최대 시간
echo preemptoff > current_tracer # 선점 비활성화 최대 시간
echo wakeup > current_tracer # 최고 우선순위 태스크 웨이크업→실행 지연
cat tracing_max_latency # 최대 지연 (us)
# ── 함수 프로파일러: 함수별 호출 횟수/시간 ──
echo nop > current_tracer
echo 1 > function_profile_enabled
# 워크로드 실행...
echo 0 > function_profile_enabled
cat trace_stat/function*
# Function Hit Time Avg
# -------- --- ---- ---
# vfs_read 1234 456.789 us 0.370 us
ftrace vs perf vs BPF: ftrace는 커널 함수 호출 흐름과 지연 시간 분석에 최적입니다. perf는 PMU 기반 통계/샘플링에, BPF는 커스텀 필터링과 집계에 강합니다. 일반적으로 "어디서 시간이 소모되나" → perf, "왜 느린가" → ftrace/BPF, "얼마나 느린가" → 모두 조합.
io_uring
io_uring은 Linux 5.1에서 도입된 비동기 I/O 인터페이스로, 공유 링 버퍼를 통해 시스템 콜 오버헤드를 줄입니다. SQPOLL+IOPOLL 조합은 특정 워크로드/디바이스 조건에서 시스템 콜 및 인터럽트 경로를 크게 줄일 수 있지만, 모든 I/O 경로에서 완전히 제거되는 것은 아닙니다.
io_uring의 상세 아키텍처, 커널 내부 구현, liburing 예제, 보안 고려사항 등은 io_uring 전용 페이지를 참고하세요.
커널 튜닝 파라미터
커널 튜닝 파라미터(sysctl)는 런타임에 커널 동작을 조정합니다. 튜닝은 반드시 측정 기반으로 진행하고, 변경 전후 벤치마크를 비교해야 합니다.
네트워크 튜닝
| 파라미터 | 기본값 | 고성능 설정 | 설명 |
|---|---|---|---|
net.ipv4.tcp_rmem | 4096 131072 6291456 | 4096 262144 16777216 | TCP 수신 버퍼 (min default max) 바이트 |
net.ipv4.tcp_wmem | 4096 16384 4194304 | 4096 262144 16777216 | TCP 송신 버퍼 (min default max) 바이트 |
net.core.rmem_max | 212992 | 16777216 | 소켓 수신 버퍼 최대값 |
net.core.wmem_max | 212992 | 16777216 | 소켓 송신 버퍼 최대값 |
net.core.somaxconn | 4096 | 65535 | listen() backlog 최대값 |
net.core.netdev_max_backlog | 1000 | 5000~30000 | NIC → 커널 큐 최대 길이 |
net.ipv4.tcp_max_syn_backlog | 2048 | 65535 | SYN_RECV 상태 최대 대기열 |
net.ipv4.tcp_tw_reuse | 2 | 1 | TIME_WAIT 소켓 재사용 (클라이언트) |
net.ipv4.tcp_fastopen | 1 | 3 | TFO 활성화 (1: 클라이언트, 2: 서버, 3: 양쪽) |
net.ipv4.tcp_congestion_control | cubic | bbr | 혼잡 제어 알고리즘 (BBR: 대역폭 기반) |
net.core.busy_poll | 0 | 50 | 소켓 busy polling 시간(us) — 지연↓ CPU↑ |
# 고성능 네트워크 튜닝 (10G+ 환경)
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 262144 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 262144 16777216"
sysctl -w net.core.netdev_max_backlog=5000
# BBR 혼잡 제어 활성화
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.core.default_qdisc=fq
# RSS (Receive Side Scaling): NIC 큐 → CPU 분산
ethtool -l eth0 # 현재 큐 수 확인
ethtool -L eth0 combined 8 # 8개 큐 설정
# RPS/RFS (소프트웨어 기반 패킷 분산)
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus # CPU 0~3
echo 32768 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt # 플로우 테이블
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
# 인터럽트 코얼레싱 (NIC별 최적화)
ethtool -c eth0 # 현재 설정
ethtool -C eth0 rx-usecs 50 tx-usecs 50 # 50us 주기로 인터럽트 통합
ethtool -C eth0 adaptive-rx on # 적응형 코얼레싱
I/O 튜닝
| 파라미터 | 경로 | 설명 |
|---|---|---|
| I/O 스케줄러 | /sys/block/sdX/queue/scheduler | none(NVMe), mq-deadline(HDD), bfq(데스크톱), kyber(서버) |
| readahead | /sys/block/sdX/queue/read_ahead_kb | 선독 크기 (기본 128KB, 순차 I/O: 2048~4096) |
| nr_requests | /sys/block/sdX/queue/nr_requests | I/O 큐 깊이 (높이면 처리량↑ 지연↑) |
| max_sectors_kb | /sys/block/sdX/queue/max_sectors_kb | 단일 I/O 요청 최대 크기 |
| rotational | /sys/block/sdX/queue/rotational | 0: SSD/NVMe, 1: HDD (스케줄러 힌트) |
# I/O 스케줄러 설정
cat /sys/block/nvme0n1/queue/scheduler # 현재 스케줄러 확인
echo none > /sys/block/nvme0n1/queue/scheduler # NVMe: none 권장
echo mq-deadline > /sys/block/sda/queue/scheduler # HDD: mq-deadline
# readahead 튜닝 (순차 읽기 워크로드)
blockdev --getra /dev/sda # 현재값 (512바이트 단위)
blockdev --setra 4096 /dev/sda # 2MB readahead (4096 × 512B)
# NVMe 최적화
echo 2 > /sys/block/nvme0n1/queue/nomerges # 병합 비활성화 (NVMe는 불필요)
echo 1024 > /sys/block/nvme0n1/queue/nr_requests # 큐 깊이 증가
# Dirty 페이지 writeback 튜닝
sysctl -w vm.dirty_ratio=40 # 동기 writeback 임계값 (%)
sysctl -w vm.dirty_background_ratio=10 # 비동기 writeback 시작 (%)
sysctl -w vm.dirty_expire_centisecs=3000 # dirty 페이지 만료 (30초)
sysctl -w vm.dirty_writeback_centisecs=500 # writeback 데몬 주기 (5초)
# filesystem mount 옵션 최적화
# ext4: noatime,nobarrier(배터리 백업 시),commit=60
# xfs: noatime,inode64,logbufs=8
# 주의: nobarrier는 전원 장애 시 데이터 손실 위험
메모리/VM 튜닝
| 파라미터 | 기본값 | 설명 |
|---|---|---|
vm.swappiness | 60 | 스왑 적극성 (0: 최소 스왑, 200: 매우 적극적) |
vm.dirty_ratio | 20 | 프로세스가 writeback 대기하는 dirty 비율(%) |
vm.dirty_background_ratio | 10 | 백그라운드 writeback 시작 임계값(%) |
vm.nr_hugepages | 0 | 정적 Huge page 수 (2MB 페이지) |
vm.overcommit_memory | 0 | 메모리 오버커밋 (0: 휴리스틱, 1: 항상, 2: 제한) |
vm.min_free_kbytes | 자동 | 비상 메모리 예약 (높이면 OOM↓ 가용 메모리↓) |
kernel.sched_latency_ns | 커널별 상이 | CFS 지연 관련 (디버그 인터페이스) |
컴파일러 최적화
커널 코드에서 사용하는 컴파일러 힌트, 최적화 속성, 빌드 옵션은 런타임 성능에 직접적인 영향을 줍니다. GCC/Clang은 커널 전용 확장과 함께 다양한 최적화 기법을 제공합니다.
분기 예측 및 코드 배치 힌트
/* ── 분기 예측 힌트 ── */
if (likely(condition)) /* __builtin_expect(!!(x), 1) → 대부분 true */
fast_path();
if (unlikely(error)) /* __builtin_expect(!!(x), 0) → 대부분 false */
handle_error();
/* static_branch: 런타임 패칭 (0 오버헤드 분기) */
DEFINE_STATIC_KEY_FALSE(my_feature);
if (static_branch_unlikely(&my_feature))
do_feature(); /* 비활성 시: NOP (분기 비용 0) */
/* 활성화 시 코드 패칭으로 분기 전환 */
static_branch_enable(&my_feature);
/* ── 코드 섹션 배치 ── */
void __hot fast_func(void); /* .text.hot 섹션 (I-캐시 친화적) */
void __cold error_func(void); /* .text.unlikely 섹션 (핫 경로에서 분리) */
void __init setup_func(void); /* .init.text → 부팅 후 메모리 해제 */
/* noinline: 인라인 방지 (콜스택 보존, 코드 크기 제어) */
noinline void debug_dump(void);
/* __always_inline: 최적화 수준과 관계없이 강제 인라인 */
static __always_inline void critical_path(void);
메모리 접근 최적화
/* ── 캐시 프리페치 ── */
prefetch(ptr); /* L1 캐시로 미리 로드 (읽기 의도) */
prefetchw(ptr); /* L1 캐시로 미리 로드 (쓰기 의도, MESI E 상태) */
/* 리스트 순회 시 다음 노드 프리페치 */
list_for_each_entry(entry, &head, list) {
prefetch(entry->list.next);
process(entry);
}
/* ── 캐시라인 정렬 ── */
struct hot_data {
atomic_t counter; /* 자주 접근하는 필드를 앞에 배치 */
unsigned long flags;
} ____cacheline_aligned; /* 캐시라인 경계에 정렬 */
/* false sharing 방지: 별도 캐시라인에 배치 */
struct per_cpu_data {
unsigned long count;
} ____cacheline_aligned_in_smp; /* SMP에서만 정렬 */
/* ── READ_ONCE / WRITE_ONCE: 컴파일러 최적화 방지 ── */
/* 공유 변수 접근 시 컴파일러의 부적절한 최적화 방지 */
int val = READ_ONCE(shared_var); /* 반드시 메모리에서 읽기 */
WRITE_ONCE(shared_var, new_val); /* 반드시 메모리에 쓰기 */
/* ── 배리어 ── */
barrier(); /* 컴파일러 배리어 (재배치 방지, CPU 배리어 아님) */
smp_rmb(); /* SMP 읽기 메모리 배리어 */
smp_wmb(); /* SMP 쓰기 메모리 배리어 */
smp_mb(); /* SMP 전체 메모리 배리어 */
LTO, PGO, AutoFDO
| 기법 | 원리 | 커널 지원 | 성능 영향 |
|---|---|---|---|
| LTO (Link-Time Optimization) | 링크 시점에 모든 오브젝트를 통합 최적화 | CONFIG_LTO_CLANG_FULL (Clang 전용, 5.12+) | 코드 크기 5~10% 감소, IPC 1~3% 향상 |
| ThinLTO | LTO의 경량 버전 (병렬 빌드 가능) | CONFIG_LTO_CLANG_THIN | Full LTO의 90% 효과, 빌드 시간 1/3 |
| PGO (Profile-Guided Optimization) | 프로파일 데이터로 핫 경로 최적화 | CONFIG_PGO_CLANG (5.12+, Clang) | 커널 전체 1~5% 처리량 향상 |
| AutoFDO | perf 샘플링 데이터를 피드백 | 실험적 (GCC + perf) | PGO 유사하나 수집이 쉬움 |
| CFI (Control-Flow Integrity) | 간접 호출 무결성 검증 | CONFIG_CFI_CLANG (LTO 필요) | 보안 강화, 약간의 성능 비용 |
# LTO 빌드 (Clang 필수)
make LLVM=1 defconfig
scripts/config -e LTO_CLANG_THIN
make LLVM=1 -j$(nproc)
# PGO 워크플로 (Clang)
# 1. 계측 빌드
scripts/config -e PGO_CLANG
make LLVM=1 -j$(nproc)
# 2. 대표 워크로드 실행 후 프로파일 수집
# (커널 부팅 → 워크로드 실행 → 프로파일 추출)
cp /sys/kernel/debug/pgo/profraw vmlinux.profraw
llvm-profdata merge -o vmlinux.profdata vmlinux.profraw
# 3. PGO 최적화 빌드
make LLVM=1 KCFLAGS="-fprofile-use=vmlinux.profdata" -j$(nproc)
# AutoFDO 워크플로 (GCC)
# 1. 표준 빌드 후 perf 프로파일 수집
perf record -b -e cycles:pp -ag -- sleep 60
# 2. create_gcov로 프로파일 변환
create_gcov --binary=vmlinux --profile=perf.data \
--gcov=vmlinux.gcov -gcov_version=2
# 3. 프로파일 기반 재빌드
make KCFLAGS="-fauto-profile=vmlinux.gcov" -j$(nproc)
성능 관련 커널 CONFIG 옵션
| CONFIG 옵션 | 효과 | 권장 |
|---|---|---|
CC_OPTIMIZE_FOR_PERFORMANCE | -O2 (기본, 균형) | 일반 워크로드 |
CC_OPTIMIZE_FOR_SIZE | -Os (코드 크기 최소화) | 임베디드, I-캐시 압박 |
FRAME_POINTER | -fno-omit-frame-pointer | 프로파일링 시 (perf 콜스택) |
DEBUG_INFO_REDUCED | 축소된 디버그 정보 | 프로덕션 (디버그+성능 절충) |
RETPOLINE | Spectre v2 완화 (간접 분기 보호) | 보안 필수, 성능 1~5% 비용 |
PAGE_TABLE_ISOLATION | KPTI (Meltdown 완화) | Intel CPU 필수, AMD는 불필요 |
PREEMPT_NONE | 선점 비활성화 | 서버 처리량 최대화 |
PREEMPT | 완전 선점 허용 | 데스크톱/RT 반응성 |
HZ_1000 | 틱 1000Hz (1ms 해상도) | 저지연 워크로드 |
HZ_250 | 틱 250Hz (4ms 해상도) | 서버 (오버헤드↓) |
NO_HZ_FULL | 틱리스 (유휴+활성) | 지연 민감 RT + isolcpus |
perf stat -d로 L1/LLC 캐시 미스율을 확인하세요. 캐시 미스가 많으면 자료구조의 캐시라인 정렬과 메모리 접근 패턴 최적화가 필요합니다. 보안 완화(KPTI, Retpoline)의 성능 비용은 perf stat -d -- before_mitigation과 비교 측정으로 정량화하세요.
BPF 기반 프로파일링
BPF(eBPF)는 커널 내부에서 안전하게 실행되는 프로그램으로, perf/ftrace보다 유연한 커스텀 프로파일링을 가능하게 합니다. bcc(BPF Compiler Collection)는 사전 작성된 도구 모음이고, bpftrace는 AWK 스타일의 간결한 프로파일링 스크립트 언어입니다.
bcc 도구 모음
| 카테고리 | 도구 | 용도 | 주요 옵션 |
|---|---|---|---|
| CPU | profile | CPU 프로파일 (스택 샘플링) | -f (Flame Graph 형식) |
runqlat | 런큐 대기 시간 히스토그램 | -m (ms), -P (PID별) | |
cpudist | On-CPU 시간 분포 | -O (Off-CPU) | |
| 메모리 | cachestat | 페이지 캐시 히트/미스율 | 인터벌(초) 지정 |
memleak | 메모리 누수 탐지 (malloc/free) | -p PID, -a (할당 추적) | |
oomkill | OOM kill 이벤트 추적 | 실시간 알림 | |
| 디스크 I/O | biolatency | 블록 I/O 지연 히스토그램 | -mD (ms, 디스크별) |
biosnoop | 개별 블록 I/O 요청 추적 | -Q (큐 시간 포함) | |
biotop | 프로세스별 블록 I/O (top 형식) | 인터벌 지정 | |
| 네트워크 | tcpretrans | TCP 재전송 추적 | -l (lossprobe 포함) |
tcpconnect | TCP 연결 시도 추적 | -t (타임스탬프) | |
tcplife | TCP 세션 수명/전송량 | 연결 시작~종료 추적 | |
| 커널 | funclatency | 함수 실행 시간 히스토그램 | -u (us), -m (ms) |
hardirqs/softirqs | 인터럽트 처리 시간 | -d (분포), -T (타임스탬프) |
# 설치 (배포판별)
sudo apt install bcc-tools bpftrace # Debian/Ubuntu
sudo dnf install bcc-tools bpftrace # Fedora/RHEL
# CPU 프로파일링 → Flame Graph
profile-bpfcc -df -p $(pidof myapp) 30 > out.folded
flamegraph.pl out.folded > cpu_flame.svg
# 런큐 지연 (스케줄링 대기 시간) 히스토그램
runqlat-bpfcc -m 10 # 10초 동안 ms 단위
# 블록 I/O 지연 시간 분석
biolatency-bpfcc -mD 10 # 밀리초 단위, 디스크별, 10초
# 페이지 캐시 효율성
cachestat-bpfcc 1
# HITS MISSES DIRTIES HITRATIO BUFFERS_MB CACHED_MB
# 12543 234 89 98.17% 128 4096
# 함수 실행 시간 분포
funclatency-bpfcc vfs_read -u 10
# → vfs_read() 실행 시간 히스토그램 (us 단위)
# Off-CPU 분석 (블로킹/슬립 시간)
offcputime-bpfcc -df -p $(pidof myapp) 30 > offcpu.folded
flamegraph.pl --color=io offcpu.folded > offcpu_flame.svg
bpftrace 원라이너
# 함수별 실행 시간 히스토그램
bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
@us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
# 시스콜별 횟수 (프로세스별)
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm, args->id] = count(); }'
# 프로세스별 페이지 폴트
bpftrace -e 'software:page-faults:1 { @[comm, pid] = count(); }'
# TCP 재전송 추적 (소스/목적지 포함)
bpftrace -e 'kprobe:tcp_retransmit_skb {
$sk = (struct sock *)arg0;
@[ntop($sk->__sk_common.skc_daddr)] = count();
}'
# 블록 I/O 크기 분포
bpftrace -e 'tracepoint:block:block_rq_issue {
@bytes = hist(args->bytes);
}'
# context switch 빈도 (프로세스별)
bpftrace -e 'tracepoint:sched:sched_switch {
@[args->prev_comm] = count();
}'
# 메모리 할당 핫스팟 (kmalloc 콜스택)
bpftrace -e 'tracepoint:kmem:kmalloc {
@bytes[kstack] = sum(args->bytes_alloc);
}'
# CPU 주파수 변경 추적
bpftrace -e 'tracepoint:power:cpu_frequency {
printf("CPU %d: %d MHz\n", args->cpu_id, args->state / 1000);
}'
BPF 프로파일링 선택 기준: (1) 사전 정의된 분석 → bcc tools (빠르게 사용). (2) 커스텀 일회성 분석 → bpftrace (간결한 스크립트). (3) 프로덕션 상시 모니터링 → libbpf + CO-RE (커널 버전 독립적, 컴파일 의존성 없음). BPF의 오버헤드는 일반적으로 매우 낮지만(~1-5%), 고빈도 이벤트(예: 모든 malloc 추적)에서는 측정 대상 자체의 성능에 영향을 줄 수 있습니다.
잠금 경쟁 분석 (lockstat)
다중 CPU 환경에서 잠금(lock) 경쟁은 확장성의 가장 흔한 병목입니다. 커널은 목적별로 다양한 잠금 메커니즘을 제공하며, 각 잠금의 경쟁 패턴을 식별하는 것이 최적화의 첫 단계입니다.
lockstat 사용법
# lockstat 활성화 (CONFIG_LOCK_STAT 필요)
echo 0 > /proc/lock_stat # 카운터 초기화
echo 1 > /proc/sys/kernel/lock_stat # 활성화 (없을 수 있음, 커널 버전에 따라)
# 워크로드 실행 후 통계 수집
cat /proc/lock_stat | head -60
# 출력 컬럼:
# class name con-bounces contestations waittime-min waittime-max waittime-total
# acq-bounces acquisitions holdtime-min holdtime-max holdtime-total
# → contestations(경쟁 횟수)와 waittime-total이 큰 잠금이 병목
# → con-bounces: 캐시라인 바운스 (다른 CPU로 전이된 횟수)
# 경쟁이 심한 잠금 Top 10 추출
awk '/^ / && NR>4 {print}' /proc/lock_stat | sort -k 4 -rn | head -10
perf lock: 상세 잠금 분석
# perf lock: 잠금 이벤트 기록 및 분석
perf lock record -- sleep 10 # 시스템 전체 잠금 이벤트 기록
perf lock record -p $(pidof myapp) -- sleep 10 # 특정 프로세스
# 잠금별 경쟁 통계
perf lock report
# Name acquired contended avg wait total wait
# rcu_node_0:lock 234567 1234 5.2us 6412us
# &mm->mmap_lock 89012 890 12.3us 10947us ← 핫
# 경쟁이 심한 잠금의 콜스택 확인
perf lock contention -b -s 10 # BPF 기반 실시간 (6.2+)
# 가장 경쟁이 심한 10개 잠금의 콜스택 표시
# lock contention 추적 (BPF)
bpftrace -e 'tracepoint:lock:contention_begin {
@[kstack] = count();
}'
# mutex 경쟁 지연 히스토그램
bpftrace -e 'tracepoint:lock:contention_begin {
@start[tid] = nsecs;
}
tracepoint:lock:contention_end /@start[tid]/ {
@us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
잠금 최적화 전략: (1) 잠금 세분화 — 하나의 큰 잠금을 여러 작은 잠금으로 분할 (예: 글로벌 → per-CPU/per-bucket). (2) 잠금 유형 변경 — 읽기 위주면 spinlock → rwlock 또는 RCU. (3) 잠금 제거 — per-CPU 변수, lockless 자료구조(RCU 리스트), atomic 연산. (4) 임계영역 축소 — 잠금 보유 중 I/O나 메모리 할당 금지. RCU 기법에 대한 상세 내용은 RCU 페이지를 참고하세요.
메모리 프로파일링
메모리 서브시스템은 CPU와 함께 성능의 양대 축입니다. 물리 메모리 할당, 가상 메모리 매핑, 페이지 캐시, slab 할당자, NUMA 배치 등 여러 계층에서 병목이 발생할 수 있으며, 각 계층에 맞는 도구로 분석해야 합니다.
vmstat / /proc/meminfo 분석
# vmstat: 1초 간격 시스템 메모리/CPU 통계
vmstat 1
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 0 0 823456 12340 456789 0 0 50 120 850 420 5 2 92 1 0
# r: 실행 가능 프로세스 (> CPU 수이면 포화)
# b: 블로킹된 프로세스 (I/O 대기)
# si/so: swap in/out (>0이면 메모리 부족)
# bi/bo: 블록 I/O (read/write)
# /proc/meminfo 핵심 항목
grep -E "^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable|AnonPages|Mapped|Shmem|HugePages)" /proc/meminfo
# /proc/vmstat: 상세 페이지 이벤트 카운터
grep -E "pgfault|pgmajfault|pswpin|pswpout|pgalloc|pgfree|compact_|oom_kill" /proc/vmstat
# pgfault: 마이너 페이지 폴트 (정상적 요구 페이징)
# pgmajfault: 메이저 페이지 폴트 (디스크 I/O 필요 → 성능 저하)
# compact_*: 메모리 컴팩션 활동 (높으면 단편화)
# PSI(Pressure Stall Information) — 메모리 압박 지표
cat /proc/pressure/memory
# some avg10=5.20 avg60=2.10 avg300=0.80 total=345678
# full avg10=1.50 avg60=0.60 avg300=0.20 total=112233
# some > 10%: 메모리 경쟁 중, full > 5%: 심각한 메모리 부족
Slab 분석
# slabtop: 실시간 slab 캐시 사용량
slabtop -o -s c # 캐시 크기순 정렬 (1회 출력)
slabtop -s a # 활성 객체수순 정렬 (실시간)
# /proc/slabinfo: 상세 slab 통계
head -2 /proc/slabinfo && sort -k 3 -rn /proc/slabinfo | head -10
# name active_objs num_objs objsize ...
# dentry 45123 48000 192
# → dentry 캐시가 가장 많은 메모리 사용 중
# SLUB 디버그 통계 (CONFIG_SLUB_STATS 필요)
cat /sys/kernel/slab/kmalloc-256/alloc_calls | head
# → 어떤 콜사이트에서 kmalloc-256 객체를 많이 할당하는지
# slab 메모리 회수 압력 조절
# vfs_cache_pressure: 100이 기본, 높이면 dentry/inode 캐시 공격적 회수
sysctl vm.vfs_cache_pressure=150
페이지 할당 추적
# 페이지 할당 tracepoint 활성화
echo 1 > /sys/kernel/tracing/events/kmem/mm_page_alloc/enable
cat /sys/kernel/tracing/trace_pipe
# → 어떤 함수에서 페이지를 할당하는지 콜스택 포함
# page_owner: 모든 페이지의 할당자 추적 (부팅 시 활성화 필요)
# 커널 부트 파라미터: page_owner=on
cat /sys/kernel/debug/page_owner | head -50
# → 각 페이지의 할당 콜스택, 할당 시점, order 확인
# → 페이지 "어디서 할당되었는데 해제되지 않았나" 추적에 유용
# /proc/buddyinfo: 버디 할당자 단편화 상태
cat /proc/buddyinfo
# Node 0, zone Normal 3245 1567 823 412 205 102 51 25 12 6 3
# → 각 열: order 0~10 (4KB~4MB) 프리 블록 수
# → 큰 order(4+)에 프리 블록이 없으면 단편화 심각
# 메모리 컴팩션 수동 트리거
echo 1 > /proc/sys/vm/compact_memory
# kmemleak: 커널 메모리 누수 탐지 (CONFIG_DEBUG_KMEMLEAK)
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak
# unreferenced object 0xffff888012345678 (size 128):
# comm "kworker/0:1", pid 123, jiffies 4294967295
# backtrace: ... → 할당 콜스택 표시
DAMON (Data Access MONitor)
DAMON(Linux 5.15+)은 커널 레벨에서 데이터 접근 패턴을 낮은 오버헤드로 모니터링하고, 결과에 기반한 자동 최적화(proactive reclaim, LRU 정렬)를 수행합니다.
# DAMON 커널 설정
CONFIG_DAMON=y
CONFIG_DAMON_VADDR=y # 가상 주소 공간 모니터링
CONFIG_DAMON_PADDR=y # 물리 주소 공간 모니터링
CONFIG_DAMON_SYSFS=y # sysfs 인터페이스
CONFIG_DAMON_RECLAIM=y # 선제적 메모리 회수
CONFIG_DAMON_LRU_SORT=y # LRU 리스트 자동 정렬
# damo 사용자 도구 설치
pip3 install damo
# 프로세스의 메모리 접근 패턴 모니터링
sudo damo record $(pidof myapp) -o damon.data
# → 5초 간격으로 메모리 영역별 접근 빈도를 기록
# 접근 패턴 시각화
sudo damo report heats -i damon.data
# → 핫/콜드 메모리 영역 히트맵
# DAMON 기반 선제적 회수 (proactive reclaim)
# 접근 빈도가 낮은 페이지를 능동적으로 회수
echo Y > /sys/module/damon_reclaim/parameters/enabled
echo 5000000 > /sys/module/damon_reclaim/parameters/min_age
# → 5초 이상 접근되지 않은 페이지를 선제 회수
NUMA 메모리 분석
# NUMA 토폴로지 확인
numactl --hardware
# available: 2 nodes (0-1)
# node 0 cpus: 0-15
# node 1 cpus: 16-31
# node distances:
# node 0 1
# 0: 10 21 ← 원격 접근이 2.1배 느림
# 1: 21 10
# 프로세스별 NUMA 메모리 배치 확인
numastat -p $(pidof myapp)
# → 노드별 할당량, 히트/미스 비율
# → other_node > 10%이면 NUMA 배치 최적화 필요
# NUMA 바인딩으로 실행
numactl --membind=0 --cpunodebind=0 ./app # 노드 0에 메모리+CPU 고정
numactl --interleave=all ./app # 메모리를 노드 간 인터리브
# 자동 NUMA 밸런싱 (커널 기능)
cat /proc/sys/kernel/numa_balancing
# 1: 활성화 (기본). 커널이 접근 패턴 분석 후 페이지 자동 이동
# 0: 비활성화 (수동 NUMA 바인딩 시)
# NUMA 밸런싱 통계
grep -E "numa_" /proc/vmstat
# numa_hit: 로컬 노드 할당 성공
# numa_miss: 원격 노드에서 할당 (성능 저하)
# numa_pages_migrated: 자동 마이그레이션된 페이지 수
# perf로 NUMA 미스 프로파일링
perf stat -e node-loads,node-load-misses,node-stores,node-store-misses -a -- sleep 10
Huge Pages 튜닝
# Huge Pages: TLB 미스 감소를 통한 성능 향상
# 정적 Huge Pages (2MB) 할당
echo 1024 > /proc/sys/vm/nr_hugepages # 1024 × 2MB = 2GB
cat /proc/meminfo | grep HugePages
# HugePages_Total: 1024
# HugePages_Free: 1024
# Hugepagesize: 2048 kB
# NUMA 노드별 할당
echo 512 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 512 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# 1GB Huge Pages (커널 부트 파라미터 필요)
# hugepagesz=1G hugepages=16 default_hugepagesz=1G
# THP (Transparent Huge Pages) 설정
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never
# always: 모든 할당에 THP 시도
# madvise: MADV_HUGEPAGE가 설정된 영역만
# never: THP 비활성화
# DB 워크로드: madvise 권장 (예측 불가 지연 방지)
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
echo madvise > /sys/kernel/mm/transparent_hugepage/defrag
# THP 컴팩션으로 인한 지연 확인
grep -E "thp_fault_alloc|thp_fault_fallback|thp_collapse_alloc" /proc/vmstat
# thp_fault_fallback이 높으면 → 단편화로 THP 실패 빈번
메모리 튜닝 파라미터 종합
| 파라미터 | 경로 | 기본값 | 설명 |
|---|---|---|---|
vm.swappiness | /proc/sys/vm/ | 60 | 스왑 적극성 (0: 거의 안 함, 200: 매우 적극적) |
vm.dirty_ratio | /proc/sys/vm/ | 20 | 프로세스가 writeback을 기다리기 시작하는 dirty 비율(%) |
vm.dirty_background_ratio | /proc/sys/vm/ | 10 | 백그라운드 writeback 시작 임계값(%) |
vm.min_free_kbytes | /proc/sys/vm/ | 자동 계산 | 비상 메모리 예약 (워터마크 조정, OOM 방지) |
vm.zone_reclaim_mode | /proc/sys/vm/ | 0 | NUMA 존 회수 정책 (1: 로컬 존 우선 회수) |
vm.vfs_cache_pressure | /proc/sys/vm/ | 100 | dentry/inode 캐시 회수 압력 (높을수록 공격적) |
vm.overcommit_memory | /proc/sys/vm/ | 0 | 메모리 오버커밋 (0: 휴리스틱, 1: 항상 허용, 2: 제한) |
vm.watermark_boost_factor | /proc/sys/vm/ | 15000 | 단편화 방지를 위한 워터마크 부스트 (0: 비활성화) |
스케줄러 튜닝
스케줄러는 CPU 시간 할당을 결정하므로 워크로드 특성에 맞는 튜닝이 성능에 직접적 영향을 미칩니다. Linux는 CFS(Completely Fair Scheduler), RT(Real-Time), Deadline 세 가지 스케줄링 클래스를 제공하며, 6.6+ 커널에서는 CFS에 EEVDF 알고리즘이 반영되었습니다.
CFS 튜너블 파라미터
| 파라미터 | 기본값 | 설명 | 튜닝 방향 |
|---|---|---|---|
base_slice_ns | 커널/CONFIG별 상이 | CFS 슬라이스 기준값 (/sys/kernel/debug/sched/) | 줄이면 반응성↑ 처리량↓ |
sched_min_granularity_ns | 750,000 (0.75ms) | 태스크 최소 실행 시간 | 줄이면 지연↓ 컨텍스트 스위치↑ |
sched_wakeup_granularity_ns | 1,000,000 (1ms) | 선점 결정 임계값 | 줄이면 선점 빈도↑ (대화형 향상) |
sched_migration_cost_ns | 500,000 (0.5ms) | CPU 마이그레이션 비용 추정치 | 높이면 마이그레이션↓ 캐시 친화도↑ |
sched_nr_migrate | 32 | 로드밸런싱 시 이동할 최대 태스크 수 | 줄이면 밸런싱 안정적, RT 지연↓ |
sched_autogroup_enabled | 1 | 세션 기반 자동 그룹화 (데스크톱 반응성) | 서버에서는 0으로 비활성화 권장 |
sched_child_runs_first | 0 | fork 후 자식 프로세스 우선 실행 | exec 빈번 시 1로 설정 (CoW 활용) |
# CFS 튜너블 확인 (커널 디버그 인터페이스)
cat /sys/kernel/debug/sched/base_slice_ns
cat /proc/sys/kernel/sched_min_granularity_ns
cat /proc/sys/kernel/sched_migration_cost_ns
# 서버 워크로드: 처리량 최적화 (컨텍스트 스위치 최소화)
sysctl kernel.sched_min_granularity_ns=10000000 # 10ms
sysctl kernel.sched_wakeup_granularity_ns=15000000 # 15ms
sysctl kernel.sched_migration_cost_ns=5000000 # 5ms (마이그레이션 억제)
# 대화형 워크로드: 반응성 최적화
sysctl kernel.sched_min_granularity_ns=300000 # 0.3ms
sysctl kernel.sched_wakeup_granularity_ns=500000 # 0.5ms
실시간(RT) 스케줄링 튜닝
RT 태스크는 CFS 태스크보다 항상 우선 실행되므로, 잘못된 설정은 시스템 전체를 멈출 수 있습니다. RT 스로틀링은 이를 방지하는 안전장치입니다.
# RT 스로틀링: RT 태스크의 CPU 독점 방지
cat /proc/sys/kernel/sched_rt_runtime_us # 기본 950000 (95%)
cat /proc/sys/kernel/sched_rt_period_us # 기본 1000000 (1초)
# → RT 태스크는 1초 중 최대 950ms만 실행, 나머지 50ms는 CFS에 양보
# RT 스로틀링 비활성화 (주의: RT 태스크가 CPU 100% 점유 가능)
echo -1 > /proc/sys/kernel/sched_rt_runtime_us
# SCHED_FIFO로 실행 (고정 우선순위, 양보 없음)
chrt -f 80 ./realtime_app
# SCHED_RR로 실행 (같은 우선순위 내 라운드로빈)
chrt -r 80 ./realtime_app
# SCHED_DEADLINE으로 실행 (주기적 실시간 태스크)
# runtime=10ms, deadline=30ms, period=30ms
chrt -d --sched-runtime 10000000 \
--sched-deadline 30000000 \
--sched-period 30000000 0 ./periodic_app
# 현재 프로세스의 스케줄링 정책 확인
chrt -p $(pidof myapp)
# pid 1234's current scheduling policy: SCHED_OTHER
# pid 1234's current scheduling priority: 0
# RT 우선순위 범위 확인
chrt -m
# SCHED_FIFO min/max priority : 1/99
# SCHED_RR min/max priority : 1/99
CPU 친화도 및 격리
# CPU 고정 (CPU affinity)
taskset -c 2,3 ./app # CPU 2,3에 고정 (실행 시)
taskset -cp 2,3 $(pidof myapp) # 실행 중인 프로세스 변경
# CPU 격리: 커널 부트 파라미터 (스케줄러/인터럽트에서 제외)
# isolcpus=2,3 — 스케줄러에서 제외
# nohz_full=2,3 — 틱리스 모드 (tick interrupt 제거)
# rcu_nocbs=2,3 — RCU 콜백을 다른 CPU로 오프로드
# irqaffinity=0,1 — IRQ를 CPU 0,1로 제한
# 인터럽트 친화도 설정 (특정 IRQ를 특정 CPU로)
echo 3 > /proc/irq/42/smp_affinity # IRQ 42를 CPU 0,1로 (비트마스크)
# 현재 인터럽트 분배 확인
cat /proc/interrupts | head -20
# cpuset(cgroup v2)으로 CPU 파티셔닝
mkdir /sys/fs/cgroup/latency-sensitive
echo "2-3" > /sys/fs/cgroup/latency-sensitive/cpuset.cpus
echo "0" > /sys/fs/cgroup/latency-sensitive/cpuset.mems
echo $$ > /sys/fs/cgroup/latency-sensitive/cgroup.procs
cgroup v2 CPU 컨트롤러
# cgroup v2: CPU 대역폭 제한
# cpu.max: "quota period" (마이크로초)
echo "100000 1000000" > /sys/fs/cgroup/mygroup/cpu.max
# → 1초(period) 중 100ms(quota) = 10% CPU
# 제한 없음 (기본값)
echo "max 100000" > /sys/fs/cgroup/mygroup/cpu.max
# CPU 가중치 (nice와 유사, 1~10000, 기본 100)
echo 200 > /sys/fs/cgroup/mygroup/cpu.weight
# → 기본(100) 대비 2배 CPU 시간 할당
# CPU 사용량 통계
cat /sys/fs/cgroup/mygroup/cpu.stat
# usage_usec: 총 CPU 사용 시간
# user_usec: 사용자 공간
# system_usec: 커널 공간
# nr_periods: 주기 수
# nr_throttled: 스로틀링 횟수
# throttled_usec: 스로틀링된 총 시간
# CPU pressure (PSI) 모니터링
cat /sys/fs/cgroup/mygroup/cpu.pressure
# some avg10=5.00 avg60=3.20 avg300=1.80 total=182345
# → some avg10 > 10이면 CPU 경쟁 심각
EEVDF 스케줄러 (6.6+)
Linux 6.6부터 CFS의 내부 알고리즘에 EEVDF(Earliest Eligible Virtual Deadline First)가 반영되었습니다. 기존 CFS의 vruntime 기반 공정 스케줄링을 유지하면서, "가상 데드라인" 개념을 추가하여 짧은 작업의 반응성을 개선합니다.
| 특성 | 기존 CFS | EEVDF (6.6+) |
|---|---|---|
| 스케줄링 기준 | 최소 vruntime | 최소 vruntime + 가상 데드라인 |
| 짧은 태스크 | vruntime만으로 공정 분배 | 데드라인이 빨라 더 빨리 선점 가능 |
| 슬라이스 결정 | sched_latency / nr_running | 요청 기반 슬라이스 + base_slice_ns |
| 선점 조건 | vruntime 차이 > granularity | eligible + 데드라인 비교 |
| 성능 영향 | — | 대화형 지연↓, 처리량 동등 |
EEVDF 실전 영향: 대부분의 워크로드에서 EEVDF는 기존 CFS와 동일하게 동작합니다. 차이가 두드러지는 경우: (1) 짧은 요청-응답 태스크(웹 서버, DB)가 긴 배치 작업과 경쟁할 때 반응성 향상, (2) sched_latency_ns 등 일부 튜너블이 EEVDF에서는 무시되거나 의미가 변경됨. 6.6+ 커널에서 기존 CFS 튜닝 스크립트를 검증 없이 적용하지 마세요.
스케줄링 성능 모니터링
# 런큐 지연 측정 (BPF): 태스크가 CPU를 기다린 시간
runqlat-bpfcc -m 10
# 출력: 대기 시간 히스토그램 (ms)
# usecs : count distribution
# 0 -> 1 : 1523 |******** |
# 2 -> 3 : 4820 |*************************|
# 4 -> 7 : 890 |**** |
# 컨텍스트 스위치 분석
perf stat -e context-switches,cpu-migrations -a -- sleep 10
# 스케줄러 이벤트 추적
perf sched record -- sleep 5
perf sched latency --sort max # 태스크별 최대 지연
perf sched timehist # 시간순 스케줄링 이벤트
perf sched map # CPU별 태스크 매핑 시각화
# PSI (Pressure Stall Information) — 리소스 부족 지표
cat /proc/pressure/cpu
# some avg10=2.50 avg60=1.80 avg300=0.90 total=823456
# → some: 하나 이상의 태스크가 CPU 대기 중인 시간 비율(%)
# → avg10 > 25%이면 CPU 부족, 스케줄러 또는 자원 확대 필요
벤치마킹 방법론
성능 측정의 신뢰도를 확보하지 않으면, 최적화의 효과를 정확히 판단할 수 없습니다. 벤치마킹은 통계적 엄밀함과 환경 제어가 핵심입니다.
벤치마크 환경 제어
# ── 노이즈 제거 스크립트 ──
# CPU 주파수 고정 (터보 부스트/절전 방지)
cpupower frequency-set -g performance
# 또는
echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
# 터보 부스트 비활성화 (Intel)
echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
# C-State 제한 (딥 슬립 방지)
# 커널 부트: processor.max_cstate=1 intel_idle.max_cstate=0
# SMT(하이퍼스레딩) 비활성화 (정밀 측정 시)
echo off > /sys/devices/system/cpu/smt/control
# ASLR 비활성화 (주소 공간 배치 변동 제거)
echo 0 > /proc/sys/kernel/randomize_va_space
# irqbalance 중지
systemctl stop irqbalance
# 백그라운드 서비스 최소화
systemctl isolate multi-user.target
# ── 반복 측정 도구 ──
# perf stat -r: 반복 실행 + 통계
perf stat -r 10 -e cycles,instructions,cache-misses ./benchmark
# → 평균 ± stddev (%, 5% 초과 시 노이즈 의심)
# hyperfine: 사용자 공간 벤치마크 도구
hyperfine --warmup 3 --min-runs 10 \
'./benchmark_v1' './benchmark_v2'
# → 통계적 비교 (평균, 중간값, 신뢰구간, p-value)
# 커널 벤치마크
perf bench sched messaging -g 20 -t -l 1000 # 스케줄러
perf bench mem memcpy -s 4GB -l 5 # 메모리 대역폭
perf bench futex lock-pi -t 8 # futex 성능
통계적 함정: (1) CoV(변동계수) > 5%이면 측정이 불안정 — 환경 노이즈를 먼저 제거하세요. (2) 이상치는 중간값(median)으로 걸러내세요. 평균만 보면 단일 스파이크에 속을 수 있습니다. (3) 작은 차이(< 3%)를 주장하려면 유의성 검정(Welch's t-test 또는 Mann-Whitney U)이 필요합니다. (4) perf stat -r의 stddev가 측정값의 1% 이내인지 확인하세요.
성능 안티패턴
커널/시스템 프로그래밍에서 자주 발생하는 성능 문제 패턴과 해결 방향입니다.
| 안티패턴 | 증상 | 진단 도구 | 해결 방향 |
|---|---|---|---|
| 과도한 시스콜 | 높은 시스콜 빈도, 높은 sys% CPU | perf trace -s, strace -c |
배치 처리, io_uring, mmap, sendfile |
| False sharing | 높은 HITM, 코어 수 증가에 비례 성능 저하 | perf c2c |
____cacheline_aligned, 구조체 패딩, per-CPU 변수 |
| Lock contention | 코어 증가 시 성능 정체/역전 | perf lock, lockstat |
잠금 세분화, RCU, lockless, per-CPU |
| NUMA 미배치 | 원격 메모리 접근 높음, IPC 저하 | numastat, pcm-numa |
numactl --membind, NUMA-aware 할당 |
| TLB thrashing | 높은 dTLB-load-misses, 큰 작업 세트 | perf stat -e dTLB-load-misses |
Huge pages (THP 또는 hugetlbfs) |
| 캐시 오염 | 핫 데이터의 L1/L2 캐시 미스율 증가 | perf stat -d, perf mem |
데이터 구조 압축, hot/cold 분리, NT store |
| 메모리 할당 폭주 | 높은 pgfault, slab 증가, kswapd CPU 소모 | vmstat, slabtop, page_owner |
오브젝트 풀, slab 캐시, 사전 할당 |
| 인터럽트 폭풍 | 높은 hi/si CPU, 인터럽트 편중 | /proc/interrupts, hardirqs |
인터럽트 코얼레싱, NAPI, RSS/RPS 분산 |
| 컨텍스트 스위치 폭주 | cs > 100K/s, 높은 sys% CPU | vmstat, perf sched |
스레드 수 축소, 비동기 I/O, 스핀 대기 |
| Writeback 스톰 | 간헐적 I/O 지연 스파이크 | iostat, vmstat (bo열) |
dirty_ratio 낮춤, dirty_expire 줄임, I/O 스케줄러 |
안티패턴 진단 실전 예제
# ── 과도한 시스콜 진단 ──
perf trace -s ./app # 시스콜별 횟수/시간 통계
# write: 1,234,567 calls → 배치 처리 또는 io_uring 고려
# futex: 890,123 calls → 락 경쟁 또는 조건 변수 남용
# ── TLB thrashing 확인 ──
perf stat -e dTLB-load-misses,dTLB-store-misses,iTLB-load-misses -a -- sleep 10
# dTLB-load-misses > 1% → Huge Pages 도입 검토
# ── 캐시 오염 패턴 확인 ──
perf stat -e L1-dcache-load-misses,L1-dcache-loads,LLC-load-misses,LLC-loads -a -- sleep 10
# L1 미스율 > 10% → 데이터 구조 캐시라인 최적화
# LLC 미스율 > 20% → 작업 세트 > LLC 크기, 메모리 BW 확인
# ── 스케줄러 병목 확인 ──
perf sched record -- sleep 10
perf sched latency --sort max
# 최대 지연 > 10ms이면 스케줄러 튜닝 또는 CPU 부족
# ── 종합 60초 진단 체크리스트 (Brendan Gregg 스타일) ──
uptime # 1. 로드 에버리지 추세
dmesg -T | tail # 2. 커널 에러/OOM
vmstat 1 5 # 3. CPU/메모리/스왑/I/O 전체 그림
mpstat -P ALL 1 3 # 4. CPU별 사용률 불균형
iostat -xz 1 3 # 5. 디스크별 I/O 통계
free -h # 6. 메모리 사용 현황
sar -n DEV 1 3 # 7. 네트워크 인터페이스 통계
sar -n TCP,ETCP 1 3 # 8. TCP 연결/재전송 통계
top -bn1 | head -20 # 9. 상위 프로세스
cat /proc/pressure/* # 10. PSI (CPU/메모리/I/O 압박)
네트워크 성능 최적화
네트워크 스택은 NIC 하드웨어부터 소켓 레이어까지 여러 계층으로 구성되며, 각 계층에서 최적화 포인트가 다릅니다.
XDP (eXpress Data Path)
XDP는 NIC 드라이버 수준에서 패킷을 처리하여, 커널 네트워크 스택을 우회합니다. DDoS 필터링, 로드밸런싱, 패킷 포워딩에서 10~100배 성능 향상이 가능합니다.
# XDP 프로그램 로드 (ip 명령)
ip link set dev eth0 xdp obj xdp_prog.o sec xdp
# XDP 모드
# xdpdrv: NIC 드라이버 내장 (최고 성능, 드라이버 지원 필요)
# xdpgeneric: 범용 (모든 NIC, 성능 제한)
# xdpoffload: NIC 하드웨어 오프로드 (Netronome 등)
ip link set dev eth0 xdpdrv obj xdp_prog.o sec xdp
# XDP 통계 확인
bpftool prog show
bpftool map dump name xdp_stats
# AF_XDP: 커널 우회 사용자 공간 패킷 처리
# → DPDK 대안, 커널 관리 유지 + 고성능
# libbpf의 xsk (XDP socket) API 사용
인터럽트 및 패킷 분산 최적화
| 기술 | 계층 | 설명 | 설정 방법 |
|---|---|---|---|
| RSS | 하드웨어 | NIC이 패킷 해시로 큐 분산 | ethtool -L eth0 combined 8 |
| RPS | 소프트웨어 | 소프트 IRQ를 CPU에 분산 (RSS 미지원 NIC) | echo ff > /sys/.../rps_cpus |
| RFS | 소프트웨어 | 패킷을 소비 스레드의 CPU로 전달 | rps_flow_cnt + rps_sock_flow_entries |
| XPS | 송신 측 | 송신 큐를 CPU에 매핑 | echo 1 > /sys/.../tx-0/xps_cpus |
| Busy polling | 소켓 | 소켓 폴링으로 인터럽트 지연 제거 | sysctl net.core.busy_poll=50 |
| GRO/GSO | 드라이버/스택 | 패킷 집계로 프로토콜 처리 횟수 감소 | ethtool -K eth0 gro on gso on |
# 네트워크 성능 진단
ethtool -S eth0 | grep -E "drop|error|miss" # NIC 드롭/에러
cat /proc/net/softnet_stat # CPU별 패킷 처리 통계
# 컬럼: processed, dropped, time_squeeze, ...
# dropped > 0: 백로그 부족 → netdev_max_backlog 증가
# time_squeeze > 0: NAPI 처리 시간 부족 → netdev_budget 증가
# NAPI 가중치/예산 조정
sysctl -w net.core.netdev_budget=600 # 기본 300, 높이면 처리량↑ 지연↑
sysctl -w net.core.netdev_budget_usecs=8000 # NAPI 최대 처리 시간(us)
# NIC 링 버퍼 크기
ethtool -g eth0 # 현재 설정
ethtool -G eth0 rx 4096 tx 4096 # 링 버퍼 확대 (드롭 방지)
# TCP 연결 추적 튜닝 (conntrack)
sysctl -w net.netfilter.nf_conntrack_max=1048576
sysctl -w net.netfilter.nf_conntrack_buckets=262144
네트워크 최적화 우선순위: (1) NIC 드롭/에러 확인 (ethtool -S) → 링 버퍼/코얼레싱. (2) 인터럽트 분산 (/proc/interrupts) → RSS/RPS. (3) TCP 튜닝 (버퍼, BBR). (4) 커널 우회가 필요하면 XDP/AF_XDP. NAPI 상세 구조는 NAPI 페이지를 참고하세요.
I/O 성능 최적화
스토리지 I/O 성능은 I/O 스케줄러 선택, readahead 정책, 파일시스템 마운트 옵션, 그리고 블록 레이어 파라미터에 의해 결정됩니다.
I/O 스케줄러 선택
| 스케줄러 | 알고리즘 | 적합 워크로드 | 디바이스 |
|---|---|---|---|
| none (noop) | 큐에 넣고 바로 전달 | 고성능 NVMe, 가상화 게스트 | NVMe, virtio-blk |
| mq-deadline | 데드라인 기반 + 배치 | DB, 지연 민감, 혼합 워크로드 | SSD, HDD 범용 |
| bfq | Budget Fair Queueing | 데스크톱 반응성, 공정 I/O | 회전 디스크, 느린 SSD |
| kyber | 토큰 기반 + 지연 목표 | 서버 고부하, 지연 SLO | 고성능 SSD |
# I/O 스케줄러 변경
cat /sys/block/sda/queue/scheduler # 현재 스케줄러 확인
echo mq-deadline > /sys/block/sda/queue/scheduler
# 영구 설정: udev 규칙
# /etc/udev/rules.d/60-io-scheduler.rules
# ACTION=="add|change", KERNEL=="sd*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
# ACTION=="add|change", KERNEL=="sd*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"
# mq-deadline 파라미터 튜닝
echo 150 > /sys/block/sda/queue/iosched/read_expire # 읽기 데드라인 (ms)
echo 5000 > /sys/block/sda/queue/iosched/write_expire # 쓰기 데드라인 (ms)
echo 16 > /sys/block/sda/queue/iosched/fifo_batch # 배치 크기
# I/O 성능 모니터링
iostat -xz 1
# 주요 지표:
# %util: 100%에 가까우면 포화 (HDD에서 중요, NVMe에서는 의미 제한적)
# await: 평균 I/O 완료 시간 (ms) — HDD: ~10ms, SSD: <1ms, NVMe: <0.1ms
# avgqu-sz: 평균 큐 깊이 — > 1이면 요청 대기 중
# r_await/w_await: 읽기/쓰기별 지연 (불균형 확인)
# Direct I/O vs Buffered I/O
# Direct I/O: 페이지 캐시 우회, DB에서 자체 버퍼 관리 시 사용
# O_DIRECT 플래그 또는 mount -o direct
# → 이중 캐싱 방지, DMA 직접 전송, 정렬 요구사항 주의
# fio: I/O 벤치마크 도구
fio --name=test --rw=randread --bs=4k --numjobs=4 \
--iodepth=32 --size=1G --filename=/dev/nvme0n1 --direct=1
# → IOPS, 대역폭, 지연 시간 분포 (p50, p99, p99.9)
실전 사례 분석
일반적인 성능 문제를 발견하고 해결하는 전체 과정을 단계별로 제시합니다.
사례 1: CPU 바운드 — IPC 저하 원인 추적
# 증상: 애플리케이션이 CPU 100% 사용하지만 예상보다 느림
# 1단계: IPC 확인
perf stat -d -- ./slow_app
# → IPC: 0.45 (매우 낮음, 메모리 스톨 의심)
# → LLC-load-misses: 15.2% (높음)
# 2단계: 핫스팟 함수 식별
perf record -g -F 99 -- ./slow_app
perf report
# → process_data() 함수가 CPU의 45% 차지
# 3단계: TMA Level 2 분석
sudo python3 toplev.py -l2 -- ./slow_app
# → Backend Bound: 62% (Memory Bound 55%, Core Bound 7%)
# 4단계: 메모리 접근 패턴 분석
perf record -e mem-loads:pp -c 10000 -- ./slow_app
perf mem report
# → 대부분의 메모리 로드가 DRAM에서 서비스됨 (L3 미스)
# 5단계: 원인 - 연결 리스트 순회 시 캐시 미스
# 해결: 배열 기반 구조로 변경 + prefetch 적용
# 결과: IPC 0.45 → 1.8, 처리 시간 75% 감소
사례 2: 지연 스파이크 — 간헐적 느림 원인
# 증상: p99 지연이 p50 대비 100배 높음 (100ms vs 1ms)
# 1단계: 시스템 전체 상태 확인
vmstat 1 30
# → 간헐적으로 bo(block out) 폭증, wa(I/O wait) 급등
# 2단계: dirty writeback 확인
grep -E "Dirty|Writeback" /proc/meminfo
# → Dirty: 1,200,000 kB (1.2GB dirty 페이지 누적)
# 3단계: 원인 — dirty_ratio 도달 시 동기 writeback
# 해결:
sysctl -w vm.dirty_ratio=10 # 동기 writeback 임계값 낮춤
sysctl -w vm.dirty_background_ratio=5 # 비동기 writeback 더 빨리 시작
sysctl -w vm.dirty_expire_centisecs=1000 # dirty 만료 10초로 단축
# 결과: p99 지연 100ms → 5ms
사례 3: 확장성 문제 — 코어 추가해도 성능 정체
# 증상: 4코어 → 32코어로 증가해도 처리량 2배에서 정체
# 1단계: 잠금 경쟁 확인
perf lock record -a -- sleep 10
perf lock report
# → global_lock: contended 89,234회, avg wait 45us
# 2단계: 경쟁 콜스택 확인
perf lock contention -b -s 5
# → 모든 스레드가 하나의 전역 해시 테이블 잠금을 경쟁
# 3단계: false sharing 확인
perf c2c record -a -- sleep 10
perf c2c report --stdio
# → shared_counter와 unrelated_field가 같은 캐시라인
# 해결:
# (1) 글로벌 잠금 → per-bucket 잠금으로 세분화
# (2) 공유 카운터 → per-CPU 카운터 + 주기적 합산
# (3) ____cacheline_aligned로 false sharing 제거
# 결과: 32코어에서 처리량 28배 (거의 선형 확장)
관련 문서
최적화와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.