코어 수와 쓰레드 수의 관계는 멀티스레드 프로그래밍에서 성능 최적화의 핵심 요소입니다. 쓰레드 수가 특정 코어 개수와 맞지 않을 때 성능이 저하되는 이유는 여러 가지가 있으며, 이는 CPU가 수행하는 컨텍스트 스위칭, 메모리 대역폭 문제, 그리고 CPU 코어의 오버헤드와 관련이 있습니다. 이를 구체적인 예시로 살펴보겠습니다.
1. 코어 개수와 쓰레드 개수의 관계
CPU는 보유한 코어 수만큼 작업을 동시에(병렬성) 수행할 수 있습니다. 예를 들어, 4개의 코어가 있는 CPU는 이론적으로 최대 4개의 쓰레드를 동시에 실행할 수 있습니다. 이보다 많은 쓰레드가 실행될 경우, 운영 체제는 TCB(Thread Control Block)을 사용해 각 쓰레드의 상태를 저장하고 복구하면서 컨텍스트 스위칭을 수행하게 됩니다. 이 과정에서 CPU는 쓰레드 상태를 저장하고 다시 로드하는 데 추가적인 시간을 소모하므로, 과도한 컨텍스트 스위칭이 발생하면 성능이 오히려 떨어질 수 있습니다.
2. 코어 개수와 쓰레드 개수의 비효율적 예시
(1) 코어 수보다 많은 쓰레드를 생성한 경우: 4코어 CPU에서 100개의 쓰레드 생성
이론적 처리 방식
4코어 CPU에서 100개의 쓰레드를 동시에 실행하려고 한다면, 운영 체제는 4개의 쓰레드를 각 코어에 배정하고 나머지 96개의 쓰레드는 대기 상태로 놓입니다. 운영 체제는 스케줄러를 통해 각 쓰레드에 주기적으로 CPU를 할당하는데, 이를 위해 쓰레드 간 상태를 저장하고 복구하는 과정이 반복됩니다.
비효율 발생 이유
- 컨텍스트 스위칭 오버헤드 증가: 100개의 쓰레드가 시분할 처리를 위해 CPU 시간을 나누어 사용해야 하므로, 각 쓰레드가 작업을 실행하는 시간보다 상태를 전환하는 데 더 많은 시간이 소요될 수 있습니다.
- 캐시 미스: 각 쓰레드가 번갈아 실행되며 CPU 캐시를 사용하는 데이터가 자주 바뀌어(제거되어) 캐시 미스가 발생합니다. 이로 인해 메모리 접근 시간이 늘어나고, CPU 성능이 저하됩니다.
- 메모리 부족: 100개의 쓰레드 스택 메모리가 동시에 할당되면, 물리 메모리의 한계로 인해 스왑(swap)이 발생할 수 있습니다. 메모리와 Disk는 대역폭에서 작게는 4배, 크게는 100배 정도 차이가 나기에 처리 성능에 문제가 발생할 수 있습니다.
(2) 코어 수보다 적은 쓰레드를 생성한 경우: 8코어 CPU에서 2개의 쓰레드 생성
이론적 처리 방식
8코어 CPU에서 2개의 쓰레드만 생성한다면, 실제로는 CPU 자원을 효율적으로 사용하지 못하게 됩니다. 두 개의 코어는 할당된 쓰레드 작업을 처리하지만, 나머지 여섯 개의 코어는 유휴 상태로 남습니다.
비효율 발생 이유
- CPU 리소스 낭비: 나머지 6개의 코어는 아무 작업도 하지 않으므로 CPU의 계산 능력을 충분히 활용하지 못합니다. 이는 특히 고성능 작업에서 중요한 CPU 자원을 낭비하는 결과를 초래합니다.
- 작업 지연: 병렬로 처리할 수 있는 작업을 충분히 활용하지 못하므로 전체 작업 완료 시간이 느려집니다. 특히 여러 작업을 동시에 처리할 수 있는 상황에서 병렬 처리를 하지 않으면, 사용자가 느끼는 응답 시간이 길어질 수 있습니다.
3. 작업 유형에 따른 쓰레드 수 최적화 예시
(1) CPU 집중 작업의 경우: 머신러닝 8코어 CPU 사용
머신러닝과 같은 CPU 집약된 작업에서는 코어 수와 동일한 쓰레드를 사용하는 것이 이상적입니다. 예를 들어, 전처리, 학습, 평가 등은 많은 계산을 필요로 하며, 각 쓰레드가 독립적인 계산 작업을 수행합니다.
- 적정 쓰레드 수 설정: 8코어 CPU에서는 8개의 쓰레드를 생성해 각 코어에 하나씩 배정합니다.
- 효율성 향상: 모든 코어가 바쁘게 작업을 처리하고, 컨텍스트 스위칭 오버헤드가 최소화되므로 성능이 최적화됩니다.
그러나, 8개의 쓰레드보다 많은 쓰레드를 사용하면 성능이 떨어지기 시작합니다. 특히, 16개의 쓰레드를 사용하는 경우에는 각 코어가 두 개의 쓰레드를 번갈아 가며 처리하게 되므로 컨텍스트 스위칭 비용이 증가하고, 실질적인 작업 시간이 줄어들게 됩니다.
(2) IO 집중 작업의 경우: 데이터베이스에서 대량의 데이터를 읽는 애플리케이션에서 4코어 CPU 사용
IO 집중 작업은 CPU 자원을 덜 사용하고 대신 Blocking I/O로 인한 대기 시간이 많이 발생하는 작업입니다. 데이터베이스 쿼리, 파일 읽기/쓰기 등이 이에 해당합니다. 이러한 작업에서는 CPU 코어 수보다 많은 쓰레드를 사용해도 성능이 저하되지 않으며, 오히려 더 많은 쓰레드가 대기 시간을 효율적으로 채울 수 있습니다.
- 적정 쓰레드 수 설정: 4코어 CPU에서 8~12개의 쓰레드를 사용하는 것이 일반적입니다. 이는 대기 시간 동안 다른 쓰레드가 작업을 수행할 수 있도록 해주기 때문에 자원을 더 효율적으로 활용할 수 있습니다.
- 효율성 향상: 쓰레드가 IO 대기 상태일 때, 다른 쓰레드가 CPU에서 작업을 수행하게 되므로 전체 처리량이 증가합니다.
반면, 20개 이상의 쓰레드를 사용하는 경우에는 컨텍스트 스위칭 오버헤드가 증가하고, CPU 캐시 메모리가 효율적으로 사용되지 않아 오히려 성능이 저하될 수 있습니다.
4. 실제 예시: 서버 애플리케이션의 쓰레드 설정
시나리오: 16코어 CPU 서버에서 웹 서버를 운영하며, 동시에 수천 명의 사용자가 접속할 수 있도록 구성해야 한다고 가정합니다. 이 서버는 HTTP 요청을 처리하고 데이터베이스와 상호작용하는 일을 동시에 처리합니다.
- 쓰레드 풀 설정: 일반적으로 코어 수의 2배에서 4배 정도의 쓰레드를 생성하는 것이 좋습니다. 따라서 16코어라면 32~64개의 쓰레드 풀을 설정하여 대기 시간을 최소화합니다.
- 비효율 방지: 과도한 쓰레드 생성 시, CPU는 오히려 컨텍스트 스위칭에 많은 시간을 소비하고, 실제 요청 처리 시간이 줄어들 수 있습니다. 이를 방지하기 위해 최적의 쓰레드 수를 설정하고 모니터링을 통해 적정 수준을 유지하는 것이 중요합니다.
5. 최적의 코어와 쓰레드 조합
코어 수와 쓰레드 개수는 작업의 유형과 성격에 따라 다르게 설정해야 합니다. 일반적인 지침은 다음과 같습니다:
- CPU 집중 작업: 코어 수와 동일한 쓰레드 수 사용.
- IO 집중 작업: 코어 수보다 더 많은 쓰레드를 사용하여 대기 시간을 효율적으로 관리.
- 혼합 작업: 코어 수의 2배 정도의 쓰레드를 사용하여 다양한 작업을 효과적으로 처리.
물론 이는 이론적인 내용이며, 꼭 성능 모니터링과 조정 과정을 통해 쓰레드 개수를 산정해야 합니다.
'Architecture' 카테고리의 다른 글
대규모 트래픽에 대비한 아키텍처 확장 전략 (0) | 2024.11.02 |
---|---|
CachedThreadPool의 한계와 ThreadPoolExecutor 커스터마이징 (0) | 2024.10.28 |
의존성 역전 원칙(DIP): 유연하고 확장 가능한 코드 설계의 핵심 (0) | 2024.02.05 |
좋은 코드를 위한 5가지 핵심 원칙: SOLID부터 리팩토링까지 (0) | 2024.01.21 |
쉽게 이해하는 SOLID 원칙: 유지보수성을 높이는 객체지향 설계 방법 (2) | 2024.01.02 |