CPU Bound 작업에서 스레드를 늘려도 처리 속도가 비례해서 빨라지지 않습니다. CPU 코어 수와 컨텍스트 스위칭 비용 때문입니다.
CPU Bound란?
이미지/영상 처리, 압축, 수치 계산, 암호화처럼 외부 대기 없이 CPU를 직접 사용하는 작업입니다. 작업이 끝날 때까지 CPU를 계속 점유하며, 연산 자체가 병목이 됩니다.
I/O Bound 작업(DB 쿼리, 네트워크 호출)과 달리 스레드가 대기 상태에 들어가지 않습니다.
1 코어 기준 동작
1 코어에서 여러 스레드를 돌리는 건 동시성(Concurrency)입니다. 실제로 동시에 실행되는 게 아니라 OS가 시간을 쪼개 번갈아 실행하는 방식입니다.
스레드 1개
CPU를 혼자 독점합니다. 컨텍스트 스위칭 없이 진행됩니다.
- CPU 사용률: 100%
- 완료 시간: T (기준)
스레드 2개
OS가 두 스레드를 번갈아 전환하는 컨텍스트 스위칭이 발생합니다. 코어가 하나라 동시에 실행될 수 없습니다.
- CPU 사용률: 100%
- 완료 시간: T + α (α는 컨텍스트 스위칭 오버헤드)
스레드를 늘려도 작업이 병렬 처리되지 않아 완료 시간이 줄지 않습니다. 오히려 전환 비용만 추가됩니다.
스레드 100개
1 코어에서 100 스레드가 경합합니다. OS 스케줄러가 끊임없이 스레드를 전환하고, 스위칭 비용이 누적되면서 처리량이 떨어집니다.
- 처리량: 감소
- CPU 사용률: 100% 유지 (상당 부분이 스위칭 비용)
CPU가 계산 외에 스레드 전환에도 상당한 시간을 쓰는 상태입니다.
스레드가 전환될 때 단순히 레지스터 값만 바뀌는 게 아닙니다. 기존 스레드가 CPU 캐시(L1, L2)에 쌓아놓은 데이터가 무효화(Cache Pollution)되어, 다시 메모리에서 읽어와야 하는 비용이 큽니다.
N 코어일 때
코어가 N개면 스레드 N개를 실제로 동시에 실행할 수 있습니다. 이를 병렬성(Parallelism)이라고 합니다. 1 코어의 동시성과 달리 시분할이 아닌 물리적 동시 실행입니다.
N 코어 + N 스레드
각 스레드가 각 코어에서 병렬 실행됩니다.
- 완료 시간 ≈ T / N (작업이 100% 병렬화 가능할 때. 실제로는 암달의 법칙에 따라 직렬 구간만큼 손실)
- 각 코어 100% (
top기준 N × 100%) - 컨텍스트 스위칭 최소
참고로 단일 스레드 작업을 N 코어 시스템에서 돌리면 한 코어만 100%이고 시스템 전체로는 1/N만 사용됩니다. top에서 200%나 400%로 찍히는 건 코어별 합산 값입니다.
N 코어 + 스레드 수 » N
2 코어에 100 스레드를 띄우면 1 코어에서 봤던 문제가 그대로 반복됩니다. 스레드 수가 코어 수를 초과하는 순간부터 경합과 스위칭 비용이 쌓이기 시작합니다.
최적 스레드 수
Brian Goetz의 Java Concurrency in Practice 공식입니다.
N_threads = N_cpu × U_cpu × (1 + W/C)
- N_cpu: 코어 수
- U_cpu: 목표 CPU 사용률 (0~1)
- W (Wait): 스레드가 I/O·lock 등으로 대기하는 시간
- C (Compute): 실제 CPU를 사용하는 시간
- W/C: 대기 / 연산 비율, 작업 성격을 나타냅니다 (CPU Bound는 ≈ 0, I/O Bound는 큼)
CPU Bound는 W/C ≈ 0이므로 N_threads ≈ N_cpu. 실무에서는 page fault나 간헐적 block을 고려해 N + 1을 권장합니다.
| 작업 유형 | 최적 스레드 수 |
|---|---|
| CPU Bound | N 또는 N+1 |
| I/O Bound | N × (1 + W/C) |
작업 성격에 따라 최적 스레드 수가 달라지며, CPU Bound는 코어 수에 수렴합니다.