Python 패키징, 속도 3배 향상! 개발 생산성 UP!
Python 패키징 라이브러리(Packaging Library)의 핵심 기능인 버전 및 스펙(Specifier) 처리 속도가 대폭 개선됨
버전(Version) 생성 및 스펙 집합(SpecifierSet) 처리 속도가 각각 최대 2배, 3배 빨라짐
Python 3.15의 통계 프로파일러(Statistical Profiler)를 활용하여 성능 병목 지점(Bottleneck)을 분석
정규 표현식(Regular Expression) 최적화, 불필요한 객체 생성 제거 등 다양한 기법 적용
커뮤니티에서는 pip 및 관련 도구의 전반적인 성능 향상에 대한 기대감을 표명
정규 표현식(Regular Expression) 최적화
저자는 PEP 440 버전 표준을 따르는 정규 표현식(Regular Expression)의 성능 개선에 집중했다. 특히, 원자적 그룹(Atomic Grouping)과 소유 한정자(Possessive Qualifier)를 활용하여 백트래킹(Backtracking)을 줄여 속도를 향상시켰다. 이러한 최적화는 버전 생성 속도를 5% 향상시키는 결과를 가져왔으며, Python 3.11.5 이상 버전에서 더욱 효과적이었다. 또한, `fullmatch`를 사용하여 앵커(Anchor)를 사용하는 `search`를 대체하여 1%의 추가적인 성능 향상을 달성했다.
불필요한 객체 생성 제거
성능 개선의 핵심은 불필요한 객체 생성을 줄이는 데 있었다. 저자는 `Version` 객체 생성 시 튜플(Tuple)을 생성하는 대신, 첫 사용 시 튜플을 생성하도록 변경하여 정렬(Sorting) 성능을 향상시켰다. 또한, `_TrimmedRelease` 클래스에서 문자열(String)을 거치지 않고 직접 서브클래스를 생성하도록 변경하여 중복된 Version 객체 생성을 방지했다. 이러한 변경을 통해 `SpecifierSet`의 성능을 37% 향상시켰다.
싱글디스패치(singledispatch) 제거
저자는 `canonicalize_version` 함수에서 `functools.singledispatch`의 사용을 지적하며, 성능에 부정적인 영향을 미친다고 분석했다. 싱글디스패치(singledispatch)는 특정 스타일의 프로그래밍에는 유용하지만, 성능이 중요한 상황에서는 적합하지 않다는 것이다. 대신 `if` 문을 사용하여 코드를 단순화하고, 불필요한 함수 호출을 줄여 성능을 개선했다. 이러한 변경을 통해 `SpecifierSet`의 성능을 7% 향상시켰다.
Map 대신 Generator 사용
저자는 `release = tuple(int(i) for i in match.group("release").split("."))` 와 같은 제너레이터(Generator) 표현식의 성능 저하를 지적하며, `map` 함수를 사용하는 것이 더 효율적이라고 제안했다. `map` 함수를 사용하면 코드 가독성을 유지하면서도 8%의 성능 향상을 얻을 수 있었다. 이는 작은 튜플(Tuple)을 처리할 때 리스트 컴프리헨션(List Comprehension)보다 더 나은 성능을 보였다.
성능 측정 및 벤치마킹 도구 활용
저자는 성능 개선을 위해 asv를 활용한 마이크로 벤치마크(Micro-benchmark)를 구축했다. 이를 통해 각 변경 사항의 성능 향상 정도를 정량적으로 측정하고, 개선 효과를 시각적으로 확인할 수 있었다. 또한, Python 3.15의 새로운 통계 프로파일러(Statistical Profiler)를 사용하여 성능 병목 지점을 정확하게 파악하고, 최적화 작업을 수행했다. 이러한 벤치마킹 과정을 통해 코드의 성능을 지속적으로 개선하고, 유지보수성을 높일 수 있었다.