-
스레드를 많이 사용할 수록 성능이 증가할까?TIL 2024. 1. 2. 23:58728x90
스레드를 많이 쓸수록 성능이 높아질까?
스레드는 프로세스 내에 존재하는 실행 단위이고, 멀티 스레드를 통해 음악을 들으면서 웹서핑을 하는 동시 작업이 가능하다. 스레드가 많을수록 동시에 처리할 수 있는 작업의 개수가 증가하는 것이기 때문에 멀티 스레드가 무조건 성능이 더 좋다고 생각하게 된다.
그러나 모든 상황에서 무조건 멀티 스레드가 싱글 스레드보다 좋다고 할 수는 없다.
1. 임계 영역에 대한 동기화 비용
멀티 스레드는 자원(전역 변수, heap 메모리 영역)을 공유하기 때문에 프로세스 생성에 비해 적은 메모리와 자원을 소모하고, 컨텍스트 스위칭도 프로세스보다 빠르다. 하지만 여러 개의 스레드가 임계 영역의 공유 자원에 접근할 수 있기 때문에, 데이터의 일관성과 정확성을 유지하기 위해 동기화 기법을 사용한다.

대표적인 동기화 기법으로는 뮤텍스와 세마포어가 있다. 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 제한하여 이를 통해 데이터에 대한 일관성을 유지한다. 하지만 이러한 동기화 기법은 스레드 간의 경쟁과 대기 상황을 발생시키므로, 성능에 부정적인 영향을 미칠 수 있다.
뮤텍스나 세마포어와 같은 동기화 기법을 사용하는 경우 스레드가 데이터에 접근하기 전에 lock을 획득하고, 데이터에 접근한 후 lock을 해제한다. 이러한 작업은 추가적인 시간이 소요되고, 남은 스레드의 실행을 중지하거나 대기하게 만드므로 프로그램의 성능이 저하될 수 있다.

그리고 CPU 캐시와 메모리 사이에 데이터 일관성 문제도 발생할 수 있다. 여러 개의 스레드가 동시에 공유하는 메모리나 데이터에 수정을 가할 때, 특정 CPU 코어의 캐시에 저장된 데이터와 다른 CPU 코어의 캐시에 저장된 데이터가 일치하지 않을 수 있다. 이럴 경우 CPU 캐시에서 데이터를 불러오는 비용이 발생한다.

요약하면 멀티 스레드 프로그램이 많은 공유 데이터를 사용할 경우, 동기화 및 캐시 일관성 작업으로 인해 병목이 일어나 성능이 떨어질 수 있다.
실제로 대부분의 프로그램에서는 많은 양의 데이터와 복잡한 로직을 처리해야하고 요즘 CPU는 기본적으로 멀티 코어를 탑재하므로, 병목 현상이 있다하더라도 멀티 스레드 프로그램에 싱글 스레드 프로그램보다 빠르다.
다만 무조건 멀티스레드가 싱글 스레드보다 성능 상승 곡선이 가파르다고 볼 수 없다.
2. 컨텍스트 스위칭 오버헤드
여러개의 프로세스나 스레드가 있을 때, CPU가 현재 프로세스나 스레드의 상태를 저장하고 다른 프로세스나 스레드로 전환될 때 발생하는 비용을 의미한다. 이 과정에서 CPU 시간과 자원을 소모하기 때문에 성능에 영향을 미치는 것이다.

멀티 프로세스 대신 멀티 스레드로 프로그램 모델을 구성하는 이유는 프로세스의 컨텍스트 스위칭 오버헤드보다 스레드의 컨텍스트 스위칭 오버헤드가 훨씬 작아 병목이 적기 때문이다.
그렇다고 해서 오버헤드의 자체 비용이 발생하지 않는 것은 아니다. 즉 싱글 스레드 모델에서는 컨텍스트 스위칭 오버헤드가 발생하지 않지만, 멀티 스레드 모델은 컨텍스트 스위칭 오버헤드가 발생하므로 스레드가 많을 수록 스위칭이 증가하기 때문에 성능이 저하될 수 있다.
싱글 코어 같은 환경에서 코어 수는 적은데 스레드를 늘리게 되면, 각 코어에서 경합하는 스레드의 수가 점점 많아지기 때문에 오버헤드로 인한 성능 한계를 경험하게 된다.

따라서 스레드가 많을수록 작업을 분리해서 동시에 처리하니까 항상 빠르다라는 것은 옳지 않다.
3. 잔여 스레드의 리소스 낭비
많은 양의 작업을 여러 개의 스레드로 빠르게 처리하는 것은 좋지만, 이용률이 적어서 스레드를 한 두개밖에 이용하지 않을 때, 나머지 잔여 스레드들이 CPU, 메모리, 네트워크 등의 자원을 불필요하게 점유해서 성능 저하나 오류의 원인이 될 수 있다.
유휴 스레드가 많을 수록 불필요하게 메모리를 차지하고 있는 것이므로 시스템 자원 낭비로 이어진다. CPU는 다른 스레드에게 CPU 시간을 양도하도록 설계되어 있어 이 유휴 스레드와 다른 스레드 간에 컨텍스트 스위칭을 해서 CPU의 효율성을 떨어트리게 된다.
즉 작업을 수행하지 않더라도 리소스를 소비하고 오버헤드를 생성시키는 문제가 발생한다.
이를 해결하기 위해서는 스레드의 개수를 관리해서 리소스 낭비를 최소화하는 것이 중요하다.
4. 어플리케이션 성격에 따른 제약
어플리케이션의 목적과 주제에 따라 아키텍처가 달라질 수 있다. 만약 순차적으로 동작이 실행되어야하거나, 잘게 쪼개서 동시에 실행하기 어려운 어플리케이션이라면 동시에 더 많은 작업을 수행하는 멀티 스레드는 이점을 잃는다.
대표적인 예시로는 CPU 바운드, I/O 바운드 어플리케이션이 있다.
cf) CPU 바운드
CPU 연산 능력에 의존하는 작업. (영상 처리 작업, 이미지 프로세싱)
cf) I/O 바운드
I/O 장치의 응답 속도에 의존하는 작업. (파일 입출력, 네트워크 통신, 데이터베이스 접근)
CPU 바운드 어플리케이션
CPU 바운드 작업에서 적절한 스레드 수는 코어 수 + 1이다. 이는 컨텍스트 스위칭 오버헤드 비용임을 유추할 수 있다. CPU를 많이 사용하기 때문에 코어수 이상으로 스레드를 늘려도 이점이 없고 오히려 컨텍스트 스위칭으로 인해 오버헤드가 더 많아져서 성능에 부정적 영향을 준다.
I/O 바운드 어플리케이션
입출력 작업이 많을 경우 스레드를 늘리는 것이 좋다. 이러한 작업들은 I/O 장치의 응답 속도에 의존하게 된다.
파일 입출력 같은 경우 파일을 읽기 위해 디스크에서 데이터를 조회하는데, 하드웨어 한계상 입출력 작업에 걸리는 시간이 오래 걸리므로 CPU는 그동한 휴무 상태가 된다. 그래서 싱글 스레드일 경우 낭비가 발생하게 된다.

멀티 스레드는 IO작업이 처리될 동안 다음에 이행할 작업을 다른 스레드에 할당하여 수행하도록 한다. 따라서 IO작업이 많은 상황에서는 CPU 코어 수보다 2-3배 혹은 그 이상으로 스레드를 늘리는 것이 코어들을 더 효율적으로 사용할 수 있기 때문에 성능 면에서 이점을 얻는다.
그렇지만 스레드를 늘리면 늘릴 수록 잔여 스레드의 컨텍스트 오버헤드와 동기화 등의 문제가 발생할 수 있다. 그래서 멀티 스레드 모델 대신 비동기 I/O 처리에 특화된 이벤트 기반 프로그래밍을 접목하기도 한다.
싱글 스레드 + 이벤트 기반 프로그래밍 모델에서는 이벤트 루프를 통해 이벤트를 감지하고, 이벤트 핸들러를 호출해서 이벤트를 처리하는 방식이다.
만약 입출력 작업을 수행해야 하는 경우, 이벤트 핸들러는 비동기 I/O를 사용해서 입출력 작업을 수행하고, 입출력 작업이 완료될 때까지 이벤트 루프를 블로킹하지 않게 된다. 따라서 작업을 처리하는 도중에도 다른 작업을 처리할 수 있어서 멀티 스레드 모델에 비해 더 적은 리소스를 사용하고 높은 처리량을 보여줄 수 있다.
728x90'TIL' 카테고리의 다른 글
Python GIL (2) 2024.01.03 멀티 프로세스 vs 멀티 스레드 (2) 2024.01.02 Thread & Process - 2 (1) 2023.12.28 Docker로 개발환경을 구축하는 이유 (0) 2023.12.27 Thread & Process - 1 (1) 2023.12.27