2. 컨텍스트 스위칭, 스케줄링, CPU 캐시/캐시라인/지역성
난이도 중내 답안
모범답안
모범답안 — 컨텍스트 스위칭, 스케줄링, CPU 캐시/지역성
난이도: 중
핵심 답변
- 컨텍스트 스위칭: CPU가 실행 중인 스레드/프로세스를 멈추고 다른 것으로 바꾸는 과정. 레지스터·프로그램 카운터·스택 포인터 등 CPU 상태를 저장/복원하고, 프로세스 전환이면 주소 공간(페이지 테이블)도 교체한다.
- 직접 비용: 커널 진입, 레지스터 저장/복원, 스케줄러 실행. 대략 수백 ns ~ 수 μs.
- 간접 비용(더 큼): 캐시·TLB가 새 컨텍스트 데이터로 다시 채워지는 동안의 미스 폭증("cache pollution"). 워킹셋이 크면 이게 직접 비용을 압도한다.
- 캐시 계층: L1(코어 전용, 가장 작고 빠름, 수십 KB) → L2(코어 전용/공유, 수백 KB) → L3(여러 코어 공유, 수~수십 MB). 아래로 갈수록 크고 느리다.
- 캐시라인: 캐시와 메모리가 주고받는 최소 단위, 일반적으로 64바이트. 한 바이트만 읽어도 그 라인 64B 전체가 올라온다.
- 지역성: 시간 지역성(방금 쓴 데이터를 곧 또 쓴다) + 공간 지역성(쓴 데이터 근처를 곧 쓴다). 캐시는 이 둘을 전제로 설계되어, 지역성이 높을수록 적중률이 오른다.
깊이 있는 설명 (메커니즘, 왜)
- 컨텍스트 스위칭의 트리거: 타임 슬라이스 소진(선점), 블로킹 I/O 대기, 락 대기, 더 높은 우선순위 스레드 도착 등.
- 간접 비용의 정체: 스위칭 직후 새 스레드는 자기 데이터가 캐시에 없어 메인 메모리(수십~수백 사이클, ~100ns)에서 다시 읽어야 한다. TLB(가상→물리 주소 변환 캐시)도 (특히 프로세스 전환 시) 무효화되어 페이지 워크가 늘어난다.
- 스케줄링
- 선점형: OS가 강제로 CPU를 회수(타임 슬라이스/우선순위). 공정성·응답성 좋지만 스위칭 빈번.
- 협력형: 스레드가 자발적으로 양보(yield)할 때만 전환. 스위칭 적고 효율적이지만 한 작업이 양보 안 하면 전체가 멈춘다(코루틴/유저레벨 스케줄링이 이 모델).
- 타임 슬라이스가 너무 짧으면: 스위칭 오버헤드 비율↑, 캐시가 계속 차가움. 너무 길면: 응답성↓, 대화형/실시간 작업이 밀린다.
- 캐시 동작: 메모리 접근 시 해당 주소가 캐시에 있으면 히트, 없으면 미스 → 라인 단위(64B) 적재. 순차 접근은 한 라인 적재로 여러 원소를 커버해 공간 지역성을 살린다. 반복 접근하는 데이터는 시간 지역성으로 캐시에 머문다.
응용/실무 연결 (게임서버에서)
- thread-per-connection 10,000개 문제
- 활성 스레드 수가 코어 수를 한참 초과하면 스케줄러가 끊임없이 스위칭한다(thrashing). CPU는 "일하는 것처럼 보이지만" 실제로는 상태 저장/복원과 캐시 재적재에 시간을 쓴다.
- 워킹셋이 코어 캐시를 넘어, 각 스레드가 깨어날 때마다 캐시 콜드 스타트 → 메모리 대기. 지연이 들쭉날쭉(jitter)해진다.
- 해법: 스레드 수를 코어 수 근처로 제한하고, 비동기 I/O + 이벤트 루프(epoll/IOCP), 스레드 풀, 또는 코루틴으로 다수 연결을 소수 스레드가 처리. C#이면 async/await + I/O 완료 포트 기반.
- 흩어진
Player*순회 문제 (AoS vs SoA, pointer chasing)- 포인터 배열을 따라가면 각 Player가 힙 곳곳에 있어 매 접근이 캐시 미스(pointer chasing). 게다가 위치 갱신에 필요 없는 필드까지 같은 캐시라인에 끌려와 캐시 낭비.
- 개선책 1: 객체 본체를 연속 배열(
std::vector<Player>/ 풀)에 담아 순차 접근 → 공간 지역성 확보. - 개선책 2: **SoA(Structure of Arrays)**로 위치만 모은
posX[],posY[],posZ[]배열을 두면 갱신 루프가 필요한 데이터만 촘촘히 읽어 캐시라인을 100% 활용하고 SIMD에도 유리. (ECS가 이 아이디어를 일반화한 것.)
흔한 오답·함정
- "스레드 많이 만들면 빨라진다" — CPU 바운드는 코어 수가 한계, 과다 스레드는 스위칭 손해.
- 컨텍스트 스위칭 비용을 "레지스터 저장 비용"으로만 보고 캐시/TLB 오염(간접 비용)을 빼먹는 것.
- 캐시라인 크기를 모르거나 4KB(페이지)와 혼동. 캐시라인은 64B, 페이지는 보통 4KB.
- "캐시는 알아서 해주니 데이터 배치는 신경 안 써도 된다" — 배치(레이아웃)가 적중률을 좌우한다.
꼬리질문 대비
- Q. false sharing이 뭔가요? A. 서로 다른 코어가 다른 변수를 쓰지만 그 변수들이 같은 64B 캐시라인에 있어, 한쪽 쓰기가 다른 코어의 캐시라인을 무효화시켜 성능이 떨어지는 현상. 핫한 per-thread 변수는 캐시라인 패딩으로 분리한다.
- Q. 스레드 풀의 적정 크기는? A. CPU 바운드면 대략 코어 수, I/O 바운드면 대기 비율을 감안해 그보다 크게. 측정 기반 튜닝이 원칙.
- Q. 프로세스 전환과 스레드 전환 비용 차이는? A. 같은 프로세스 내 스레드 전환은 주소 공간을 공유해 페이지 테이블/TLB 비용이 적고, 프로세스 전환은 주소 공간 교체로 TLB 플러시까지 동반해 더 비싸다.