회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
본문은 요즘IT와 번역가 David가 함께 아서 파스텔(Arthur Pastel)의 글 <State of Python 3.13 Performance: Free-Threading>을 번역한 글입니다. 필자는 파리에서 활동하는 Python 개발자이자, 오픈소스 애호가로 MongoDB를 위한 ODMantic을 개발했습니다. 현재는 CI 파이프라인에서 성능 문제를 예방하는 솔루션 ‘CodSpeed’의 창업자로 활동하고 있습니다. 이 글에서는 파이썬 3.13에서의 주요 성능 변화와 ‘Free-threading’ 기능을 소개합니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
본문은 요즘IT와 번역가 David가 함께 아서 파스텔(Arthur Pastel)의 글 <State of Python 3.13 Performance: Free-Threading>을 번역한 글입니다. 필자는 파리에서 활동하는 Python 개발자이자, 오픈소스 애호가로 MongoDB를 위한 ODMantic을 개발했습니다. 현재는 CI 파이프라인에서 성능 문제를 예방하는 솔루션 ‘CodSpeed’의 창업자로 활동하고 있습니다. 이 글에서는 파이썬 3.13에서의 주요 성능 변화와 ‘Free-threading’ 기능을 소개합니다.
필자에게 허락을 받고 번역했으며, 글에 포함된 각주(*표시)는 ‘번역자주’입니다.
지난 10월에 출시된 파이썬 3.13은 최근 릴리스 중에서도 성능 향상에 가장 큰 중점을 둔 버전입니다. 릴리스 노트를 살펴보면 성능에 큰 영향을 미칠 수 있는 주요 변경 사항들이 눈에 띕니다.
mimalloc
* 할당자를 포함하고 있습니다.
*GIL: 한 번에 하나의 스레드만 파이썬 코드를 실행할 수 있도록 하는 잠금 장치
*JIT: 프로그램 실행 중에 자주 사용되는 코드를 실시간으로 기계어로 번역해 성능을 향상시키는 컴파일 기술
*mimalloc: Microsoft가 개발한 고성능 메모리 할당자로, 일반 malloc보다 더 빠르고 메모리 단편화가 적은 최신 메모리 관리 라이브러리
*malloc: 프로그램 실행 중에 필요한 메모리를 동적으로 할당받는 C 언어의 기본 메모리 할당 함수
이 글에서는 free-threaded 모드를 중점적으로 살펴보고, 이러한 변화가 파이썬 애플리케이션의 성능에 미치는 영향을 측정해 볼 예정입니다.
Free-threading은 파이썬 3.13에서 도입된 실험적 기능으로, 파이썬이 전역 인터프리터 잠금(GIL) 없이 실행될 수 있도록 하는 기능입니다. GIL은 여러 스레드가 동시에 파이썬 바이트코드를 실행하는 것을 막는 상호 배제(mutex) 장치입니다. 이러한 설계는 파이썬의 메모리 관리를 단순화하고, C API 사용을 쉽게 만들어주었지만, 현대의 멀티코어 프로세서를 효과적으로 활용하는 데 있어 가장 큰 장애물 중 하나로 작용해 왔습니다.
전통적으로는 multiprocessing
모듈을 사용하여 이 문제를 해결해 왔습니다. 이 모듈은 스레드 대신 별도의 파이썬 프로세스를 생성하는 방식을 사용합니다. 이러한 접근 방식이 동작은 하지만, 다음과 같은 중요한 제약 사항들이 있습니다.
이러한 제약 사항을 실제로 확인하기 위해 PageRank 알고리즘의 구현 사례를 살펴보겠습니다. PageRank는 초기 구글의 검색 엔진을 지원했던 알고리즘으로, 다음과 같은 특성으로 인해 이상적인 예시가 됩니다.
파이썬 3.12 이전 버전에서 단순한 멀티스레드 구현을 시도할 경우, 행렬 연산 과정에서 GIL로 인한 병목 현상이 발생하게 됩니다. 한편 멀티프로세싱 방식을 사용할 경우에는 다음과 같은 문제에 직면하게 됩니다.
다음으로 다양한 동시성 모델을 통한 구현 방법을 살펴보도록 하겠습니다.
이 알고리즘에서 계산 비용이 가장 많이 드는 부분은 색칠된 두 곳입니다. 첫 번째는 진입 노드들로부터의 점수 기여분을 계산하는 부분이고, 두 번째는 댐핑 팩터를 적용하여 새로운 점수를 최종 결과에 반영하는 부분입니다. 이 중에서 첫 번째 부분을 병렬화하는 것이 가장 효과적이면서도 구현하기 쉬운 방법이 될 것입니다. 범위를 분할하여 여러 스레드가 new_scores
배열을 효율적으로 계산할 수 있기 때문입니다.
멀티스레드 구현에서는 먼저 행렬을 여러 개의 청크*로 나누는 것부터 시작합니다.
*청크: 큰 데이터를 작은 조각으로 나눠서 하나씩 처리하는 방식
그런 다음 각 스레드는 행렬의 서로 다른 청크에 대해 작업을 수행하며, 새로운 점수를 갱신합니다.
여기서 주목할 점은 new_scores
배열의 갱신이 잠금 된 상태에서 이루어진다는 것입니다. 이는 경쟁 상태를 방지하기 위한 것입니다. 잠금 상태가 오래 유지되면 병목 현상이 될 수 있지만, 실제로는 알고리즘의 첫 번째 부분을 병렬화하는 것만으로도 상당한 성능 향상을 얻을 수 있습니다.
마지막으로 각 스레드에 청크를 할당하여 처리합니다.
멀티프로세스 구현은 기본적으로 멀티스레드 구현과 매우 유사합니다. 주요 차이점들을 살펴보겠습니다.
new_scores
배열을 갱신하는 대신 local_scores
배열을 반환합니다. 그 후 메인 프로세스에서 로컬 점수들을 취합합니다.
이 방식은 멀티스레드 버전보다 빠를 수 있지만, 프로세스 간 통신에 따른 오버헤드가 발생합니다. 특히 대규모 데이터셋의 경우 이 오버헤드가 상당히 커질 수 있습니다.
ThreadPoolExecutor
대신 multiprocessing.Pool
을 사용합니다. API는 매우 비슷하지만, multiprocessing.Pool
은 스레드 대신 프로세스 풀을 생성합니다.
실제 성능 변화를 측정하기 위해 성능 테스트를 구축해 보겠습니다. 우선 테스트용 데이터를 생성하는 것부터 시작합니다.
여기서는 실행마다 동일한 결과를 보장하기 위해 고정된 시드값을 사용합니다. 이는 서로 다른 구현 방식의 성능을 비교할 때 매우 중요합니다. 페이지 간의 가짜 연결을 생성하여 현실적인 그래프를 만들고 있지만, 행렬의 크기가 동일하다면 빈 행렬을 사용하더라도 수학적 연산은 정확히 동일할 것입니다.
다음으로, pytest-codspeed
라는 pytest
플러그인을 사용하여 다양한 매개변수와 여러 파이썬 버전/빌드에 대한 성능을 측정해 보겠습니다.
여기서는 3가지 구현 방식을 3가지 다른 그래프 크기로 테스트합니다. pytest-codspeed
가 제공하는 benchmark
를 사용하여, 주어진 인자로 pagerank
함수의 실행 시간을 측정합니다.
CodSpeed의 인프라에서 다양한 파이썬 빌드의 성능을 측정하기 위한 깃허브 액션 워크플로우를 작성합니다.
이 설정에서는 파이썬 3.12, 3.13, 그리고 free threading 지원이 포함된 3.13에 대해 GIL을 활성화한 경우와 비활성화한 경우 모두에서 벤치마크를 실행합니다. 이를 통해 GIL이 활성화된 상태에서도 free-threading의 영향을 확인할 수 있습니다.
multiprocessing
구현의 한계도 명확히 드러났는데, 프로세스 간 통신 오버헤드로 인해 오히려 단일 스레드 구현보다 더 느린 결과를 보여주었습니다.threading
기반 구현이 가장 빠른 성능을 보여주었습니다. GIL이 더 이상 스레드의 병렬 실행을 제한하지 않게 되었기 때문입니다.*SAI: 프로그램 실행 중에 코드를 분석하고 최적화하는 특별한 종류의 인터프리터
다른 모든 그래프 크기에서도 결과는 매우 유사했으며, 동일한 결론에 도달했습니다. 이번 측정을 통해 파이썬 3.13의 새로운 free-threaded 빌드가 병렬 애플리케이션의 성능에 상당한 영향을 미칠 수 있으며, multiprocessing
의 매우 유의미한 대안이 될 수 있음을 확인했습니다. 다만 아직은 실험적인 기능이며, 전반적인 성능 저하로 인해 프로덕션 환경에서 사용하기에는 이른 단계이지만, 올바른 방향으로 나아가는 매우 유망한 진전이라고 할 수 있습니다.
이번 벤치마크에는 Python 3.12에서 도입된 GIL 없이 Python 코드를 병렬로 실행하는 또 다른 방식인 subinterpreters*는 포함되지 않았습니다. Subinterpreters는 대부분의 경우에서 다른 접근 방식들보다 느린 것으로 확인되었는데, 이는 주로 데이터 공유와 워커 간 통신 문제가 아직 완전히 해결되지 않았기 때문입니다. 하지만 이러한 문제들이 해결된다면, multiprocessing
의 훌륭한 대안이 될 수 있을 것입니다.
*subinterpreters: 하나의 프로세스 안에서 여러 개의 완전히 독립된 파이썬 인터프리터를 실행해 GIL 없이 진정한 병렬 처리를 가능하게 하는 파이썬 3.12의 기능 중 하나.
<원문>
State of Python 3.13 Performance: Free-Threading
위 번역글의 원 저작권은 Arthur Pastel에게 있으며, 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다