[Final] PEP 709 - Inlined comprehensions
원문 링크: PEP 709 - Inlined comprehensions
상태: Final 유형: Standards Track 작성일: 24-Feb-2023
PEP 709 – 인라인 컴프리헨션 (Inlined Comprehensions)
개요 (Abstract)
현재 Python의 리스트, 딕셔너리, 셋 컴프리헨션(comprehensions)은 내부적으로 중첩된 함수(nested functions)로 컴파일됩니다. 이는 컴프리헨션의 이터레이션 변수(iteration variable)를 격리하는 장점을 제공하지만, 런타임 성능에서는 비효율적입니다. 이 PEP는 리스트, 딕셔너리, 셋 컴프리헨션을 정의된 코드 위치에 인라인(inline)하여, 스택(stack)에 충돌하는 지역 변수(locals)를 푸시(push)/팝(pop)하는 방식으로 필요한 격리를 제공할 것을 제안합니다.
이 변경으로 인해 컴프리헨션은 훨씬 빨라집니다. 단독 컴프리헨션 마이크로벤치마크(microbenchmark)에서는 최대 2배, 실제 코드에서 파생된 벤치마크에서는 11%의 속도 향상을 보였습니다.
동기 (Motivation)
컴프리헨션은 Python 언어에서 널리 사용되는 인기 있는 기능입니다. 현재 컴프리헨션을 중첩 함수로 컴파일하는 방식은 컴파일러의 단순함을 위해 사용자 코드의 성능을 희생합니다. 컴파일러의 복잡성을 약간만 증가시키면, 모든 컴프리헨션 사용자에게 거의 동일한 의미론(semantics)을 제공하면서 훨씬 나은 런타임 성능을 제공할 수 있습니다.
근거 (Rationale)
인라이닝(inlining)은 많은 언어에서 흔히 사용되는 컴파일러 최적화 기법입니다. Python에서 함수 호출을 컴파일 타임에 범용적으로 인라인하는 것은 런타임에 호출 대상이 변경될 수 있으므로 거의 불가능합니다. 그러나 컴프리헨션은 특별한 경우로, 컴파일러가 정적으로 알고 있는 호출 대상(call target)이 변경될 수 없고(바이트코드 직접 조작 제외), 외부로 노출되지도 않습니다.
인라이닝은 또한 다른 바이트코드 컴파일러 최적화가 더 효과적으로 작동하도록 돕습니다. 이전에는 컴프리헨션 바이트코드가 불투명한 호출이었던 반면, 이제는 컴파일러가 이를 “꿰뚫어 볼(see through)” 수 있게 되기 때문입니다.
일반적으로 성능 개선은 PEP를 필요로 하지 않지만, 이 경우 가장 간단하고 효율적인 구현이 사용자에게 가시적인 일부 영향을 미치므로, 이는 단순한 성능 개선을 넘어 언어에 대한 (작은) 변경 사항으로 간주됩니다.
명세 (Specification)
이 PEP에 따라, 컴파일러는 더 이상 컴프리헨션에 대해 별도의 코드 객체를 생성하거나, 일회용 함수 객체를 생성 및 호출(MAKE_FUNCTION, CALL)하지 않습니다. 대신, 컴프리헨션 코드가 포함하는 함수 내부에 직접 인라인됩니다.
예를 들어, def f(lst): return [x for x in lst]
와 같은 함수는:
- 이전: 컴프리헨션
<listcomp>
를 위한 별도의 코드 객체가 생성되고, MAKE_FUNCTION 및 CALL을 통해 호출되었습니다. 매 호출마다 새로운 일회용 함수 객체가 할당되고, Python 스택에 새로운 프레임(frame)이 생성 및 소멸되었습니다. - 이후: 컴프리헨션은
f()
함수의 바이트코드 내부에 직접 포함됩니다. 새로운 opcode인 LOAD_FAST_AND_CLEAR와 STORE_FAST의 조합을 통해x
와 같은 이터레이션 변수의 격리가 이루어집니다. LOAD_FAST_AND_CLEAR는 컴프리헨션 실행 전에 외부x
값을 스택에 저장하고, STORE_FAST는 실행 후 해당 값을 복원합니다.
외부 스코프(outer scope) 변수에 접근하는 경우, 인라이닝은 이 변수들을 셀(cell)에 배치할 필요를 없애고, 컴프리헨션 및 외부 함수 내의 모든 코드가 이를 일반적인 fast locals로 접근할 수 있게 하여 추가적인 성능 향상을 제공합니다.
모듈 또는 클래스 스코프(module or class scope)에 나타나는 컴프리헨션도 인라인됩니다. 이 경우 컴프리헨션은 이터레이션 변수에 대해 fast-locals ( LOAD_FAST / STORE_FAST )를 사용하여 격리를 유지합니다.
결과적으로 컴프리헨션은 로컬 변수가 완전히 격리되지만, 호출(call)의 성능 비용이나 스택 프레임 진입 없이 하위 스코프(sub-scope)를 도입합니다.
제너레이터 표현식(Generator expressions)은 현재 이 PEP의 참조 구현(reference implementation)에서는 인라인되지 않습니다. 미래에는 반환된 제너레이터 객체가 누출되지 않는 일부 제너레이터 표현식이 인라인될 수 있습니다. 비동기 컴프리헨션(Asynchronous comprehensions)은 동기 컴프리헨션과 동일하게 인라인됩니다.
하위 호환성 (Backwards Compatibility)
컴프리헨션 인라이닝은 다음과 같은 가시적인 동작 변경을 초래합니다.
-
locals()
에 외부 변수가 포함됩니다. 컴프리헨션 내에서locals()
를 호출하면 컴프리헨션을 포함하는 함수의 모든 지역 변수가 포함됩니다. 이전:[{'.0': <list_iterator object...>, 'x': 1}]
(여기서.0
은 내부 구현 세부 사항) 이후:[{'lst': [1], 'x': 1}]
(외부lst
변수 포함,.0
제거) -
트레이스백(tracebacks)에 컴프리헨션 프레임이 나타나지 않습니다. 이전: 스택 트레이스에
<listcomp>
에 대한 전용 프레임이 있었습니다. 이후:<listcomp>
에 대한 추가 프레임이 제거되어 트레이스백이 더 간결해지며,f
함수의 프레임에 컴프리헨션에 대한 올바른 줄 번호가 표시되므로 유용한 정보는 손실되지 않습니다. -
트레이싱(Tracing)/프로파일링(profiling) 시 컴프리헨션에 대한 호출/반환이 표시되지 않습니다. 리스트/딕셔너리/셋 컴프리헨션이 더 이상 중첩 함수 호출로 구현되지 않으므로,
sys.settrace
나sys.setprofile
을 사용한 트레이싱/프로파일링 시 호출 및 반환이 발생했음을 반영하지 않게 됩니다.
다른 Python 구현에 미치는 영향 (Impact on other Python implementations)
GraalPython 및 PyPy 대표들의 의견에 따르면, 이러한 가시적인 동작 변경에 적응해야 할 필요성을 느낄 가능성이 높습니다. 그러나 이러한 변경 사항은 (적어도 GraalPython의 경우) “큰 어려움 없이” 관리할 수 있을 것으로 예상됩니다.
교육 방법 (How to Teach This)
컴프리헨션 구문이 중첩 함수를 생성하고 호출할 것이라는 것은 직관적으로 명확하지 않습니다. 이전 동작에 익숙하지 않은 새로운 사용자들에게는 이 PEP의 새로운 동작이 더 직관적이고 설명이 덜 필요할 것으로 예상됩니다. (예: “정의하지도 않은 <listcomp>
라인이 왜 트레이스백에 나타나는가?”, “locals()
에서 보이는 이 .0
변수는 무엇인가?”).
거부된 아이디어 (Rejected Ideas)
- 인라이닝 없이 더 효율적인 컴프리헨션 호출: 새로운 opcode를 도입하여 일회용 함수 객체 생성 없이 간소화된 방식으로 컴프리헨션을 “호출”하지만, 여전히 새로운 Python 프레임을 생성하는 대안적 접근 방식이 있었습니다. 이는 위에서 언급된 가시적인 모든 효과를 피하고, 성능 이점의 약 절반(마이크로벤치마크 1.5배 개선, pyperformance 벤치마크 4% 개선)을 제공합니다. 또한, 이는
_PyInterpreterFrame
구조체에 새 포인터를 추가하고 각 프레임 생성 시Py_INCREF
를 필요로 하여 모든 코드에 (매우 작은) 성능 비용을 발생시킵니다. 이 PEP는 완전한 인라이닝이 행동 변화를 정당화하기에 충분한 추가 성능을 제공한다고 판단합니다.
참조 구현 (Reference Implementation)
이 PEP는 CPython main
브랜치에 대한 PR 형태로 참조 구현이 있으며, 모든 테스트를 통과했습니다. 이 참조 구현은 컴프리헨션 마이크로벤치마크에서 기존보다 1.96배 빠르며, 실제 코드를 사용하는 pyperformance 벤치마크에서는 11% 더 빠른 성능을 보였습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments