HyperAI초신경
Back to Headlines

Python 3.13t, GIL 제거로 DataFrame 처리 속도 2배 향상

2일 전

Python에서 DataFrame의 성능을 향상시키는 새로운 접근법: 불변 데이터프레임과 자유 스레딩 DataFrame의 각 행에 함수를 적용하는 것은 매우 흔한 작업입니다. 이러한 연산은 독립적으로 수행될 수 있어 병렬 처리가 가능합니다. 다중 코어 CPU를 활용하면 많은 행을 동시에 처리할 수 있습니다. 그러나 최근까지 Python에서는 이 기회를 활용할 수 없었습니다. CPU-bound 작업으로 알려진 멀티 스레딩 함수 적용은 글로벌 인터프리터 락(GIL)에 의해 제약을 받았습니다. 그러나 Python 3.13의 실험적인 자유 스레딩 빌드가 출시되면서 GIL이 제거되고, CPU-bound 작업의 진정한 멀티 스레딩 동시성 실행이 가능해졌습니다. 성능 향상 효과는 상당합니다. 자유 스레딩 Python을 활용하면 StaticFrame 3.2에서 DataFrame의 행마다 함수를 적용하는 작업이 단일 스레딩 실행보다 최소 두 배 이상 빠르게 수행됩니다. 예를 들어, 1백만 개의 정수로 구성된 정사각형 DataFrame의 각 행에서 모든 짝수 값의 합을 계산하는 작업을 lambda s: s.loc[s % 2 == 0].sum() 함수로 수행할 때, Python 3.13t (자유 스레딩 변종)에서는 처리 시간이 60% 이상 감소하여 21.3ms에서 7.89ms로 단축되었습니다. ```python Python 3.13.5 실험적인 자유 스레딩 빌드 (main, 2025년 6월 11일, 15:36:57) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin import numpy as np; import static_frame as sf f = sf.Frame(np.arange(1_000_000).reshape(1000, 1000)) func = lambda s: s.loc[s % 2 == 0].sum() %timeit f.iter_series(axis=1).apply(func) 21.3 ms ± 77.1 μs per loop (mean ± std. dev. of 7 runs, 10 loops each) %timeit f.iter_series(axis=1).apply_pool(func, use_threads=True, max_workers=4) 7.89 ms ± 60.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) ``` StaticFrame에서는 iter_series(axis=1) 인터페이스를 사용하여 행별 함수 적용을 수행할 수 있습니다. apply() 메서드는 단일 스레딩 적용을, apply_pool() 메서드는 멀티 스레딩(use_threads=True) 또는 멀티 프로세싱(use_threads=False) 적용을 지원합니다. 자유 스레딩 Python의 성능 향상은 다양한 DataFrame 모양과 구성에서도 일관되게 나타납니다. MacOS와 Linux에서 비례적으로 성능 향상이 이루어지며, DataFrame 크기가 커질수록 더욱 긍정적으로 확대됩니다. 반면, 일반 Python에서 GIL이 활성화된 상태로 CPU-bound 작업을 멀티 스레딩으로 처리하면 성능이 저하됩니다. 예를 들어, 동일한 작업을 단일 스레딩으로 수행할 때 17.7ms가 소요되었지만, 멀티 스레딩으로 수행하면 거의 40ms로 증가하였습니다. ```python Python 3.13.5 (main, 2025년 6월 11일, 15:36:57) [Clang 16.0.0 (clang-1600.0.26.6)] import numpy as np; import static_frame as sf f = sf.Frame(np.arange(1_000_000).reshape(1000, 1000)) func = lambda s: s.loc[s % 2 == 0].sum() %timeit f.iter_series(axis=1).apply(func) 17.7 ms ± 144 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit f.iter_series(axis=1).apply_pool(func, use_threads=True, max_workers=4) 39.9 ms ± 354 μs per loop (mean ± std. dev. of 7 runs, 10 loops each) ``` 自由 스레딩 Python을 사용할 때 단일 스레딩 처리가 더 느릴 수 있는 점은 고려해야 합니다. 예제에서 보듯이, Python 3.13t에서는 21.3ms가 소요되지만, Python 3.13에서는 17.7ms가 소요됩니다. 자유 스레딩 Python은 일반적으로 성능 오버헤드를 초래하지만, Python 3.14t 이후로 개선이 예상됩니다. 또한 많은 C 확장 패키지들이 3.13t를 위한 사전 컴파일된 바이너리 휠을 제공하고 있지만, 스레드 경쟁이나 데이터 레이스 등의 위험성이 여전히 존재합니다. StaticFrame은 불변성을 강제하여 이러한 위험을 피합니다. 스레드 안전성이 내재되어 있어 락이나 방어적 복사를 필요로 하지 않습니다. StaticFrame은 불변 NumPy 배열(flags.writeable를 False로 설정)을 사용하고 원위치 변환을 금지하여 이를 구현합니다. DataFrame 성능 테스트 확장 복잡한 데이터 구조인 DataFrame의 성능 특성을 평가하기 위해서는 다양한 유형의 DataFrame을 테스트해야 합니다. 다음 성능 패널은 9개의 다른 DataFrame 유형에서 행별 함수 적용을 테스트하며, 세 가지 모양과 세 가지 유형 동질성을 포함한 모든 조합을 테스트합니다. 고정된 요소 수(예: 1백만 개)에서 세 가지 모양(tall, square, wide)을 테스트합니다. 유형 동질성을 다양화하기 위해 세 가지 범주의 합성 데이터를 정의합니다: 열별(columnar, 인접한 열이 같은 유형을 갖지 않음), 혼합(mixed, 네 개의 인접한 열이 같은 유형을 공유함), 균일(uniform, 모든 열이 같은 유형을 가짐). StaticFrame은 인접한 열이 같은 유형일 경우 2차원 NumPy 배열로 표현하여 열 탐색과 행 형성 비용을 줄입니다. 균일한 경우, 전체 DataFrame을 하나의 2차원 배열로 표현할 수 있습니다. 합성 데이터는 frame-fixtures 패키지를 사용하여 생성됩니다. 테스트에 사용된 함수는 lambda s: s.loc[s % 2 == 0].sum()입니다. NumPy를 직접 사용하는 더 효율적인 구현이 가능하지만, 이 함수는 많은 중간 Series가 생성되는 일반적인 애플리케이션을 근사합니다. 자유 스레딩 Python 3.13t에서의 멀티 스레딩 함수 적용 아래에 보듯이, Python 3.13t에서의 멀티 스레딩 처리 성능 향상 효과는 모든 테스트된 DataFrame 유형에서 일관됩니다. 처리 시간은 최소 50% 감소하며, 일부 경우 80% 이상 감소합니다. 특히, 작은 행을 빠르게 처리하는 tall DataFrame에서는 추가 스레드 오버헤드가 성능을 저하시킬 수 있으므로 최적 스레드 수(max_workers 매개변수)가 적은 편입니다. 1억 개 요소(1e8)의 DataFrame으로 확장하면 성능 향상 효과가 더욱 두드러집니다. 대부분의 DataFrame 유형에서 처리 시간이 70% 이상 감소하며, 두 유형을 제외하고는 모두 성능 향상이 이루어졌습니다. 플랫폼별 멀티 스레딩 오버헤드는 크게 다릅니다. 모든 경우에서 자유 스레딩 Python의 성능 향상 비율은 MacOS와 Linux에서 비례적으로 일관되며, MacOS는 약간 더 큰 혜택을 보이는 경향이 있습니다. 1억 개 요소의 Linux에서의 처리 시간도 비슷한 상대적인 성능 향상을 보였습니다. 1만 개 요소(1e4)의 작은 DataFrame에서도 자유 스레딩 Python에서의 멀티 스레딩 처리가 유리합니다. wide DataFrame에는 이점이 없지만, tall과 square DataFrame의 처리 시간은 반으로 줄어듭니다. GIL이 활성화된 표준 Python 3.13에서의 멀티 스레딩 함수 적용 자유 스레딩 Python 이전에는 CPU-bound 애플리케이션의 멀티 스레딩 처리가 성능 저하를 초래했습니다. 아래 테스트에서 보듯이, 표준 Python 3.13에서는 동일한 테스트를 수행할 때 멀티 스레딩이 성능을 저하시킵니다. GIL이 활성화된 표준 Python 3.13에서의 멀티 프로세싱 함수 적용 자유 스레딩 Python 이전에는 멀티 프로세싱이 CPU-bound 동시성의 유일한 옵션이었습니다. 그러나 멀티 프로세싱은 프로세스당 작업량이 프로세스 생성 비용과 데이터 복사 비용을 상쇄할 정도로 충분할 때만 이점을 제공했습니다. 아래에 보듯이, 행별 함수 적용을 멀티 프로세싱으로 수행하면 성능이 크게 저하됩니다. 단일 프로세싱 대비 프로세스 시간이 2배에서 10배로 증가합니다. 각 작업 단위가 너무 작아 멀티 프로세싱 오버헤드를 상쇄할 수 없습니다. 자유 스레딩 Python의 현황 PEP 703, "CPython에서 글로벌 인터프리터 락을 선택적으로 제거하기"는 2023년 7월 Python Steering Council에서 승인되었습니다. Python 3.13에서는 실험적이고 기본 설정이 아닌 상태로 도입되었으며, Python 3.14에서는 비실험적이고 공식적으로 지원되는 상태로 진전될 예정입니다. CPython 개발의 성공으로 Python 3.14는 Python 3.10보다 20%에서 40% 더 빠른 것으로 알려져 있습니다. 그러나 DataFrame 작업에서는 성능 향상 효과가 C 확장 라이브러리(Numpy, Arrow 등)에 크게 의존하여 실제적으로 실현되지 않았습니다. 자유 스레딩 Python은 저렴하고 메모리 효율적인 스레드를 사용하여 효율적인 병렬 실행을 가능하게 합니다. C 확장 라이브러리에서도 성능 향상이 이루어져 50%에서 90%의 처리 시간 감소를 달성합니다. 불변 데이터 구조를 스레드 간 안전하게 공유할 수 있게 되면서 성능 개선의 기회가 크게 늘어났습니다. 결론 행별 함수 적용은 시작에 불과합니다. 그룹-바이(group-by) 연산, 윈도우 함수 적용 등 불변 DataFrame에서 동시에 수행 가능한 많은 작업들이 유사한 성능 향상 효과를 보일 것으로 예상됩니다. 자유 스레딩 Python은 저렴하고 메모리 효율적인 스레드를 통해 성능을 향상시키는 데 성공했으며, C 확장 라이브러리에서도 유의미한 성능 개선을 가져올 것입니다. 이제는 성능 향상의 가능성에 대한 기대가 커지고 있습니다. 업계 전문가들은 자유 스레딩 Python이 DataFrame 처리에서 획기적인 변화를 가져올 것이라고 평가합니다. StaticFrame과 같은 라이브러리는 불변성을 강조하여 스레드 안전성을 보장하며, Python 3.14t 이후로 더욱 신뢰성 있는 지원을 받을 예정입니다. 자유 스레딩 Python은 데이터 처리 분야에서의 성능 향상을 크게 견인할 것으로 기대됩니다.

Related Links