Context switching은 CPU가 하나의 작업에서 다른 작업으로 전환하는 과정입니다. 현재 process나 thread의 register, program counter 같은 실행 상태를 저장하고, 다음에 실행할 작업의 상태를 복원합니다. 이 덕분에 하나의 CPU core에서도 여러 작업이 번갈아 실행되는 것처럼 보입니다.
하지만 전환 자체는 실제 일을 처리하는 시간이 아니므로 오버헤드입니다. 특히 process 전환은 address space 전환과 cache/TLB 영향 때문에 thread 전환보다 더 무거울 수 있습니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
Context switch 시 실제로 무엇이 저장되고 복원되나요?
CPU 레지스터 전체, program counter(다음 실행할 명령어 주소), stack pointer가 PCB(Process Control Block)에 저장됩니다. 프로세스 전환이라면 추가로 페이지 테이블 베이스 레지스터(x86의 CR3 등)도 바뀝니다. 이 값이 바뀌면 MMU가 참조하는 주소 공간 자체가 교체됩니다.
저장과 복원이 완료되면 CPU는 새 프로세스의 마지막 실행 지점에서 이어서 실행합니다.
Context switching은 언제 발생하나요?
크게 두 경우로 나뉩니다.
자발적 전환(voluntary): 프로세스가 I/O를 기다리거나, sleep하거나, mutex를 얻으려다 block될 때 스스로 CPU를 내놓습니다. I/O를 기다리는 동안 CPU를 낭비하지 않기 위한 효율적인 전환입니다.
비자발적 전환(involuntary): time slice(time quantum)이 끝났거나 더 높은 우선순위 프로세스가 ready 상태가 될 때 OS 스케줄러가 강제로 전환합니다. 현대 OS는 선점형 스케줄링으로 한 프로세스가 CPU를 독점하지 못하게 합니다.
Process context switch가 thread보다 무거운 이유는?
프로세스 전환 시 주소 공간이 바뀌어 TLB(Translation Lookaside Buffer) 전체를 flush해야 합니다. 이후 새 프로세스가 메모리에 접근할 때마다 TLB miss가 발생해 page table을 다시 탐색해야 합니다. CPU L1/L2 cache도 이전 프로세스의 데이터로 차 있어 cold start 효과가 생깁니다.
같은 프로세스 안 스레드 전환은 주소 공간이 동일해 TLB를 flush할 필요가 없습니다. 레지스터 상태만 교체하면 되므로 훨씬 가볍습니다.
Context switching이 많으면 왜 성능이 나빠지나요?
세 가지 비용이 중첩됩니다.
직접 비용: 레지스터 저장/복원, 스케줄러 실행, 경우에 따른 TLB flush 자체가 수백-수천 사이클을 소모합니다.
cache 오염: 전환 전 프로세스가 L1/L2 cache에 올려둔 데이터가 교체 후 쓸모없어집니다. 새 프로세스가 실행되며 cache를 채우는 동안 cache miss가 늘어납니다.
TLB miss 증가: 주소 변환 캐시가 초기화되면 초기 메모리 접근들이 page table 탐색 비용을 지불해야 합니다.
Thread 수를 늘리면 context switching 비용은 어떻게 달라지나요?
CPU core 수보다 runnable thread가 많아질수록 스케줄러가 전환해야 하는 빈도가 늘어나고, 각 thread가 한 번 실행되는 시간이 줄어듭니다. 전환 오버헤드가 실제 연산 비율을 넘어서면 throughput이 떨어집니다.
경험적으로 CPU-bound 작업은 thread 수를 core 수에 맞추고, I/O-bound 작업은 I/O 대기 비율을 고려해 더 많이 설정합니다.
코루틴은 context switching 문제를 어떻게 줄이나요?
코루틴은 OS 스케줄러 없이 사용자 공간에서 협력적으로 전환합니다. await나 yield 지점에서만 전환이 일어나고, 전환 비용은 함수 호출 수준으로 낮습니다. TLB flush나 커널 진입이 없습니다.
수만 개의 연결을 동시에 처리해야 하는 서버에서 스레드 대신 async I/O를 쓰는 이유입니다. Node.js 이벤트 루프, Python asyncio, Go goroutine이 이 구조입니다. 단, 코루틴이 CPU를 독점하는 계산을 길게 실행하면 다른 코루틴이 실행되지 못하므로 CPU-bound 작업에는 적합하지 않습니다.
부가 설명
음악을 재생하면서 브라우저를 쓰고, 동시에 editor가 동작하는 상황을 생각할 수 있습니다. CPU는 여러 작업을 아주 빠르게 번갈아 실행하고, 운영체제 scheduler가 어떤 작업을 얼마나 실행할지 결정합니다.
전환이 너무 잦으면 실제 작업보다 전환 비용이 커질 수 있습니다.
context switching은 concurrency를 가능하게 하지만 공짜가 아닙니다. 그래서 thread를 많이 만들면 항상 빨라지는 것이 아닙니다. CPU-bound 작업에서는 core 수보다 훨씬 많은 thread가 오히려 scheduling overhead를 만들 수 있습니다.
한 줄 정리
context switching은 CPU가 실행 대상을 바꾸기 위해 현재 상태를 저장하고 다음 상태를 복원하는 과정입니다.