CPU가 실행 중인 대상을 바꾸며 상태를 저장하고 복원하는 것.
무엇을 저장하나
- ① 현재 실행 중인 것의 상태를 PCB(또는 TCB)에 저장한다
- 레지스터 · 프로그램 카운터 · 스택 포인터
- ② 스케줄러가 다음 대상을 고른다
- ③ 그 대상의 상태를 복원한다
- ④ (프로세스가 바뀌면) 페이지 테이블도 교체한다
진짜 비용은 간접 비용이다
-
직접 비용 — 레지스터 저장·복원 — 수백 나노초
-
간접 비용 — 캐시가 식는다 (새 작업의 데이터를 다시 읽어야 한다)
- TLB 가 플러시된다 (프로세스 전환 시)
- 분기 예측기가 초기화된다
- 전환 직후 한동안 느리게 돈다
-
간접 비용이 직접 비용의 몇 배가 되는 것이 보통이다
"전환 자체는 마이크로초인데 체감은 그보다 크다" 는 이 때문이다.
언제 일어나나
- 타임 슬라이스를 다 썼다 (선점)
- I/O 를 기다리게 됐다 (자발적 양보)
- 더 높은 우선순위가 준비됐다
- 인터럽트가 들어왔다
- 시스템 콜에서 블로킹됐다
측정하기
vmstat 1
# r b swpd free ... in cs us sy id wa
# 2 0 0 1.2G 12000 45000 30 10 60 0
# ^^^^^ ^^^^^ 초당 인터럽트 · 컨텍스트 스위치
pidstat -w -p <pid> 1 # 프로세스별 전환 횟수
# cswch/s 자발적 (I/O 대기 등)
# nvcswch/s 비자발적 (선점당함) ← 이게 크면 CPU 경합이 심하다
nvcswch/s가 크다는 건 실행할 준비가 된 작업이 CPU보다 많다는 뜻이다 — 스레드 수를 줄이거나 코어를 늘려야 한다.
TLB 플러시를 피하는 기법
ASID / PCID
- TLB 항목에 '어느 주소 공간의 것인지' 태그를 붙인다
- 프로세스가 바뀌어도 전부 버리지 않아도 된다
- 현대 CPU 는 대부분 지원한다
스레드가 많으면 왜 느려지나
스레드 10개 · 코어 4개 → 계속 돌아가며 전환한다
- 각 스레드가 조금씩 일하고 캐시가 계속 식는다
- 처리량이 오히려 떨어진다
그래서 CPU 바운드 작업의 스레드 수는 코어 수 근처가 최적이다
면접 함정
- ❌ "컨텍스트 스위치는 무조건 나쁘다" → 없으면 멀티태스킹이 불가능하다. 과도한 것이 문제다.
- ❌ "스레드 전환은 공짜" → 페이지 테이블 교체가 없을 뿐 캐시는 여전히 식는다.
줄이는 방법
- ① 스레드 수를 코어 수 근처로 (CPU 바운드)
- ② CPU 친화도(affinity)로 스레드를 특정 코어에 고정한다
- 캐시가 유지된다
- ③ 락 경합을 줄인다 — 락 대기가 곧 전환이다
- ④ 배치로 처리한다 — 작업 단위가 작으면 전환 비율이 높아진다
taskset -c 0,1 ./app # 코어 0,1 에만 돌린다
numactl --cpunodebind=0 --membind=0 ./app # NUMA 노드까지 고정
NUMA에서 더 아프다
멀티 소켓 서버는 CPU 마다 '가까운 메모리' 가 다르다
스레드가 다른 소켓으로 옮겨 가면
- 원래 쓰던 메모리가 이제 '먼 메모리' 가 된다
- 접근 지연이 2배 가까이 늘 수 있다
"서버를 큰 걸로 바꿨는데 성능이 안 올랐다" 의 흔한 원인
numactl --hardware # 노드 구성과 노드 간 거리
numastat -p <pid> # 이 프로세스가 어느 노드 메모리를 쓰나