2024. 12. 12. 15:43ㆍc,c++
나의 첫 궁금증은 예전에 들었던 "멀티 스레드를 제대로 쓰려면 파이썬이아니라 c++로 해야한다는 말이었다.
거기에 대해 궁금해져서 검색해보았다.
파이썬과 C++에서 멀티스레드의 차이는 주로 파이썬의 **GIL(Global Interpreter Lock)**이라는 특성과 관련이 있습니다. 이 차이가 멀티스레드의 효율성과 활용도에 큰 영향을 미칩니다.
GIL(Global Interpreter Lock)란?
- 파이썬 인터프리터는 하나의 스레드만 실행하도록 하는 락(lock) 메커니즘입니다.
- 멀티스레드를 사용하더라도 동시에 실행되는 작업은 하나의 스레드로 제한됩니다.
- 따라서, 파이썬 멀티스레드는 CPU 집약적인 작업(예: 복잡한 수학 연산)에서는 성능이 제한됩니다.
결과:
- 파이썬에서 멀티스레드는 I/O 작업(예: 파일 읽기/쓰기, 네트워크 작업)에는 효과적이지만, CPU를 많이 사용하는 작업에는 큰 성능 향상을 기대하기 어렵습니다.
C++에서의 차이점
- C++은 GIL과 같은 제약이 없으므로, 멀티스레드로 실행되는 작업들이 실제로 다중 CPU 코어를 활용할 수 있습니다.
- CPU 집약적인 작업(예: 이미지 처리, 복잡한 로봇 계산)에서도 스레드마다 병렬 실행이 가능합니다.
- ROS 2의 C++ API는 멀티스레드와 함께 작동하도록 설계되어, 고성능 요구사항이 있는 로봇 애플리케이션에 적합합니다.
결과:
- C++에서 멀티스레드는 CPU와 메모리를 효율적으로 활용하므로, 복잡한 계산과 실시간 로봇 제어에서 매우 효과적입니다.
ROS 2에서의 적용
- 파이썬의 멀티스레드 사용:
- 장점: 코드가 간결하고, I/O 중심의 작업에서 효율적.
- 단점: GIL로 인해 CPU 집약적인 작업에서는 성능 제한.
- C++의 멀티스레드 사용:
- 장점: 다중 코어를 활용하여 고성능 작업을 병렬 처리.
- 단점: 코드가 복잡하며 메모리 관리를 수동으로 해야 하는 경우가 많음.
ROS 2의 멀티스레드 스피너
- ROS 2는 MultiThreadedExecutor를 통해 멀티스레드 처리를 지원합니다.
- 파이썬: 멀티스레드 실행이 가능하지만, GIL로 인해 효과가 제한적.
- C++: GIL이 없으므로, MultiThreadedExecutor를 통해 콜백이 진정한 병렬로 실행 가능.
언제 C++을 선택해야 할까?
- 실시간 제어, 고성능 연산이 필요한 로봇 애플리케이션(예: SLAM, 고속 로봇 팔 제어).
- CPU 집약적인 병렬 처리가 필요한 경우.
- 하드웨어 리소스를 최대한 활용해야 하는 상황.
언제 파이썬을 선택해야 할까?
- 개발 속도가 중요한 초기 프로토타이핑 단계.
- 주로 I/O 중심의 작업(예: 메시지 로깅, 센서 데이터 스트리밍).
- 복잡한 병렬 처리가 필요하지 않은 경우.
결론
파이썬의 멀티스레드는 GIL로 인해 CPU 병렬 처리에 제한이 있지만, I/O 중심 작업에는 여전히 유효합니다. 반면, C++에서는 GIL이 없으므로 진정한 병렬 처리가 가능하며, 고성능 로봇 애플리케이션에 적합합니다. 따라서, 고성능이나 실시간 제어가 필요하다면 C++를, 빠른 개발이 목적이라면 파이썬을 사용하는 것이 적절합니다.
GIL이 무엇인가?
GIL은 파이썬이 멀티스레드를 사용할 때, 동시에 하나의 스레드만 실행되도록 제한하는 잠금 장치입니다. 쉽게 말해, 파이썬 인터프리터가 하나의 작업만 처리하도록 막아놓은 장치입니다.
비유로 이해하기
- GIL 없는 환경(C++): 여러 사람들이 독립된 작업을 하며 동시에 일할 수 있는 공장과 같음. 각 사람이 자유롭게 기계를 사용할 수 있음.
- GIL 있는 환경(파이썬): 기계를 사용할 때 **열쇠(GIL)**가 필요함. 한 번에 한 사람만 기계를 사용할 수 있고, 다른 사람들은 열쇠가 풀릴 때까지 기다려야 함.
GIL이 생긴 이유
- 파이썬의 메모리 관리(Memory Management):
- 파이썬은 동적으로 메모리를 관리하며, 이를 위해 내부적으로 참조 카운트를 사용함.
- 여러 스레드가 동시에 메모리를 수정하면 충돌이 발생할 수 있음. GIL은 이런 충돌을 방지하기 위해 도입됨.
- 복잡성을 줄임:
- GIL이 있으면 메모리 관리를 간단히 처리할 수 있음.
- 하지만 이는 멀티스레드를 사용하는 CPU 집약적인 작업에서 성능 저하로 이어짐.
GIL의 영향
- I/O 중심 작업(파일 읽기, 네트워크 작업 등):
- GIL이 있어도 성능에 큰 영향을 미치지 않음.
- I/O 작업 중에는 GIL이 자동으로 잠금을 해제하므로 다른 스레드가 실행될 수 있음.
- CPU 중심 작업(복잡한 계산, 이미지 처리 등):
- GIL 때문에 멀티스레드로 작업하더라도 CPU 코어를 여러 개 사용할 수 없음.
- 파이썬에서는 병렬 작업이 실제로 병렬적으로 실행되지 않고 순차적으로 실행됨.
GIL 우회 방법
- 멀티프로세싱:
- GIL은 각 프로세스마다 독립적이므로, 프로세스를 여러 개 생성하면 GIL의 영향을 받지 않음.
- 파이썬의 multiprocessing 라이브러리를 사용해 병렬 처리를 구현할 수 있음.
- C/C++ 확장:
- CPU 집약적인 작업은 GIL이 없는 C/C++로 구현해 성능을 높일 수 있음.
- GIL이 없는 대안 사용:
- GIL이 없는 다른 언어(예: C++, Go)를 사용해 고성능 작업을 구현.
GIL은 항상 문제인가?
아니요. GIL은 단일 스레드 또는 I/O 중심 작업에서는 큰 문제가 되지 않습니다. 하지만 CPU 집약적인 멀티스레드 작업에서는 성능 한계가 분명해집니다.
1. 통신(I/O) 또는 입출력(I/O) 작업
- GIL이 영향을 적게 미침:
- 네트워크 통신, 파일 입출력, 데이터베이스 작업 등은 I/O 바운드 작업으로 간주됩니다.
- I/O 작업 중에는 파이썬이 GIL을 자동으로 해제하므로, 다른 스레드가 실행될 수 있습니다.
- 이로 인해 멀티스레드의 장점을 활용할 수 있습니다.
예시
- 네트워크 요청 처리(서버-클라이언트 통신)
- 센서 데이터 수집
- 로그 파일 쓰기/읽기
2. CPU 중심 작업
- GIL이 성능의 병목이 됨:
- 이미지 처리, 데이터 분석, 수학적 계산 등 CPU를 많이 사용하는 작업에서는 GIL이 작업의 병렬 실행을 제한합니다.
- 멀티스레드를 사용하더라도 동시에 하나의 스레드만 실행되므로, 멀티스레드의 효과가 거의 없습니다.
- 이런 작업에서는 멀티프로세싱을 사용하는 것이 더 효과적입니다.
예시
- 머신러닝 모델 학습
- 영상 처리
- 복잡한 수학 연산
3. 해결 방법
CPU 중심 작업에서도 병렬 처리를 활용하려면 GIL의 제한을 우회해야 합니다. 몇 가지 방법은 다음과 같습니다:
(1) 멀티프로세싱 활용
- 멀티프로세싱은 GIL의 영향을 받지 않으며, CPU 코어를 효율적으로 활용할 수 있습니다.
- multiprocessing 모듈을 사용해 여러 프로세스에서 작업을 병렬로 실행.
(2) C/C++로 작성된 라이브러리 사용
- NumPy, TensorFlow와 같은 라이브러리는 내부적으로 GIL의 영향을 받지 않는 C/C++로 작성되어 있어 병렬 처리가 가능합니다.
(3) Cython이나 Numba 사용
- Python 코드를 컴파일된 C 코드로 변환하여 GIL을 제거하고 고성능 처리를 가능하게 합니다.
(4) ROS 2에서 C++ 사용
- ROS 2의 C++ 노드는 GIL 같은 제한이 없으므로, CPU 중심의 멀티스레드 작업에 적합합니다.
4. 파이썬 멀티스레드와 멀티프로세싱 비교
특징멀티스레드멀티프로세싱
GIL의 영향 | GIL로 인해 CPU 중심 작업에 효과 제한 | GIL의 영향을 받지 않음 |
I/O 중심 작업 | 효과적 | 덜 적합 |
CPU 중심 작업 | 효과 제한적 | 적합 |
메모리 사용량 | 공유 메모리를 사용해 효율적 | 프로세스별 별도 메모리 사용 |
병렬 처리 | 한 번에 하나의 스레드만 실행 | 진정한 병렬 처리 가능 |
결론
파이썬의 멀티스레드는 I/O 중심 작업에 효과적이지만, CPU 중심 작업에서는 GIL로 인해 성능이 제한됩니다. CPU 집약적인 병렬 처리가 필요하다면 C++, 멀티프로세싱, 또는 Cython과 같은 방법을 사용하는 것이 좋습니다.
멀티스레드가 멀티프로세싱보다 더 자주 사용되는 이유는 주로 다음과 같은 장점과 용도 때문입니다:
1. 메모리 효율성
- 멀티스레드는 같은 프로세스 내에서 작동하므로, 스레드 간에 메모리를 공유합니다.
- 예를 들어, 데이터 구조(리스트, 딕셔너리 등)를 여러 스레드가 공유할 수 있어 추가적인 데이터 복사가 필요하지 않습니다.
- 멀티프로세싱은 각 프로세스가 독립된 메모리를 사용하므로, 데이터를 서로 주고받으려면 직렬화(예: pickle)가 필요하며, 이로 인해 오버헤드가 발생합니다.
2. 속도
- 멀티스레드는 메모리 공유로 인해 스레드 간 통신이 멀티프로세싱보다 훨씬 빠릅니다.
- 멀티프로세싱에서는 프로세스 간 데이터 통신이 상대적으로 느립니다. 이는 운영체제가 프로세스 간 통신(IPC, Inter-Process Communication)을 처리하기 때문입니다.
- 특히, I/O 중심 작업에서는 멀티스레드가 더 나은 성능을 제공합니다.
3. 코드 복잡성
- 멀티스레드는 코드가 비교적 단순합니다.
- 같은 프로세스 내에서 작동하므로, 공유 변수와 데이터 구조를 바로 사용할 수 있습니다.
- 멀티프로세싱은 독립적인 프로세스를 실행하므로, 데이터를 공유하려면 추가 작업(큐, 파이프 등)이 필요합니다.
4. 운영체제의 프로세스 관리
- 프로세스를 생성하고 관리하는 데 더 많은 리소스가 필요합니다.
- 운영체제는 프로세스마다 독립된 메모리 공간을 할당하므로, 프로세스를 여러 개 생성하면 더 많은 메모리를 소비합니다.
- 스레드는 가벼운 작업 단위이므로, 운영체제에서 스레드 관리가 상대적으로 효율적입니다.
5. 사용 사례에 적합
- 멀티스레드는 I/O 바운드 작업(네트워크 요청, 파일 읽기/쓰기 등)에 매우 적합합니다.
- I/O 작업은 대부분 대기 시간이 많으므로, GIL이 영향을 덜 미칩니다.
- 멀티프로세싱은 CPU 집약적인 작업(복잡한 계산, 이미지 처리 등)에 적합하지만, 모든 상황에서 사용하기에는 오버헤드가 크고 복잡합니다.
언제 멀티프로세싱을 사용해야 할까?
- GIL의 영향을 피해야 하는 CPU 중심 작업이 있는 경우.
- 독립된 작업을 처리하거나 서로 다른 작업을 완전히 분리할 때.
- 하드웨어 성능을 최대한 활용해야 하는 경우(예: 머신러닝 모델 학습).
결론
멀티스레드는 메모리 공유와 운영 효율성 때문에 I/O 중심 작업과 일반적인 멀티태스킹 환경에서 더 자주 사용됩니다. 반면, 멀티프로세싱은 CPU 집약적 작업에 더 적합하며, GIL의 영향을 받지 않아 병렬 처리가 필요한 작업에 유리합니다.
'c,c++' 카테고리의 다른 글
로봇공학에서 Python 대신 C++을 많이 쓰는 이유 (0) | 2024.10.17 |
---|