# Python 3.14 tail-call(꼬리 호출) 인터프리터의 성능

> Clean Markdown view of GeekNews topic #19679. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=19679](https://news.hada.io/topic?id=19679)
- GeekNews Markdown: [https://news.hada.io/topic/19679.md](https://news.hada.io/topic/19679.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-03-11T09:48:19+09:00
- Updated: 2025-03-11T09:48:19+09:00
- Original source: [blog.nelhage.com](https://blog.nelhage.com/post/cpython-tail-call/)
- Points: 3
- Comments: 1

## Topic Body

- CPython 프로젝트는 최근 바이트코드 인터프리터의 새로운 구현 전략을 도입. 초기 결과는 다양한 플랫폼에서 평균 10-15%의 성능 향상을 보여줬음  
- 그러나 이 성능 향상은 주로 LLVM 19의 회귀 문제를 우회한 결과였음. 더 나은 기준(예: GCC, clang-18, 특정 튜닝 플래그가 있는 LLVM 19)과 비교했을 때 성능 향상은 1-5%로 감소  
  
### 성능 결과  
  
- 여러 컴파일러와 구성 옵션을 사용하여 CPython 인터프리터의 여러 빌드를 벤치마크했음. Intel 서버와 Apple M1 Macbook Air에서 테스트했음.  
- 모든 빌드는 LTO와 PGO를 사용했음. `clang18`을 기준으로 `pypeformance`/`pyperf compare_to`에서 보고된 평균을 사용했음.  
- **컴파일러 성능 비교**  
  - **Apple M1 Macbook Air**  :   
    - clang18: 기준  
    - clang19: 1.12배 느림  
    - clang19.taildup: 1.02배 느림  
    - clang19.tc: 1.00배 느림  
    - gcc: N/A  
- 꼬리 호출 인터프리터는 여전히 clang-18과 비교하여 속도 향상을 보였지만, clang-19로 이동하면서의 속도 저하가 더 극적이었음.  
  
### LLVM 회귀  
  
#### 간단한 배경  
- 전통적인 바이트코드 인터프리터는 `while` 루프 내의 `switch` 문으로 구성됨. 대부분의 컴파일러는 `switch`를 점프 테이블로 컴파일함.  
- 현대 C 컴파일러는 레이블의 주소를 취하고 이를 "계산된 goto"로 사용하는 패턴을 지원함. CPython은 꼬리 호출 작업 전까지 이 패턴을 사용했음.  
  
#### LLVM 19 회귀  
- LLVM 19는 tail-duplication 패스에 제한을 두어, IR 크기가 특정 한계를 초과할 경우 중복을 중단하도록 했음. 이로 인해 CPython에서는 모든 디스패치 점프가 병합되어 계산된 `goto` 기반 구현의 목적이 완전히 무산됨.  
  
##### 추가 이상 현상  
- 꼬리 호출 중복 논리의 변경이 회귀를 초래했음을 확신하지만, 회귀의 **크기**를 완전히 설명할 수는 없음.  
- 현대 프로세서에서는 2-4%의 속도 향상이 더 일반적임.  
  
##### 계산된 goto가 필요한가?  
- `clang19.nocg` 벤치마크는 `clang19`보다 **빠르다고** 주장함. 이는 컴파일러가 `switch` 기반 인터프리터를 사용하여 동일한 최적화를 수행할 수 있음을 보여줌.  
  
#### 수정  
  
- LLVM 풀 리퀘스트 114990이 회귀를 수정했음. 이 수정은 예상 성능을 복원함.  
  
### 반성  
  
#### 벤치마킹에 대하여  
  
- 시스템 최적화 시 벤치마크와 벤치마킹 방법론을 구성하고, 제안된 변경 사항을 평가함.  
- 벤치마크는 특정 데이터 포인트를 일반화하기 위해 더 많은 가정과 믿음을 필요로 함.  
  
##### 기준선  
  
- 새로운 솔루션이나 방법을 제안할 때, "현재 가장 잘 알려진 접근 방식"과 비교하는 것이 일반적임.  
  
#### 소프트웨어 엔지니어링에 대하여  
  
- 소프트웨어 시스템은 복잡하고 상호 연결되어 있으며, 빠르게 변화하고 있음.  
- 최적화 컴파일러는 프로그래머의 의도를 존중하면서도 코드를 최적화해야 하는 긴장 관계에 있음.  
  
##### 최적화 컴파일러  
  
- `musttail` 속성은 최적화와 관련된 새로운 종류의 컴파일러 기능을 나타냄. 이는 성능에 민감한 코드를 작성하는 데 더 강력한 스타일을 제공할 수 있음.  
  
#### `nix`에 대한 한 가지 더  
  
- `nix`는 이 프로젝트에서 매우 유용했음. 여러 버전의 Python 인터프리터를 관리하고 빌드하는 데 큰 도움이 되었음.

## Comments



### Comment 35694

- Author: neo
- Created: 2025-03-11T09:48:19+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=43317592) 
* 안녕하세요. 저는 CPython에 tail-calling 인터프리터를 도입한 PR의 작성자임
  - 먼저, 이 문제의 근본을 찾기 위해 거의 한 달을 소비한 Nelson에게 감사의 말을 전하고 싶음
  - 또한, 이런 큰 실수를 저질러 매우 부끄럽고 죄송함을 느끼고 있음
  - 우리가 사용한 컴파일러에 이런 버그가 있을 줄은 CPython 팀도 예상하지 못했음
  - 사과 블로그 게시물을 여기에 올렸음: [링크](https://fidget-spinner.github.io/posts/apology-tail-call.html)

* 벤치마킹은 정말로 잘하기 어려운 작업임
  - 최근에 알고리즘을 약 15% 더 빠르게 만드는 방법을 발견했음
  - 그러나 테스트 중에 더 빠른 버전의 함수를 호출하지 않고도 원래 코드가 15% 더 빨라짐
  - 이는 코드와 메모리 배치 문제로, CPU 캐시와의 정렬이 더 잘 맞았기 때문임
  - Casey Muratori가 이런 주제에 대해 흥미로운 시리즈를 진행 중임

* 저자가 이 문제의 진실을 파헤친 것에 찬사를 보냄
  - Python 3.14의 tail-call 인터프리터는 여전히 좋은 개선점임
  - 이 사건은 벤치마킹의 엄격함과 다양한 환경에서의 테스트 중요성을 가르쳐 주었음
  - 또한, 이제 모든 사람에게 이익이 될 수 있는 컴파일러 버그를 발견하게 됨
  - 얼마나 많은 "X% 더 빠름" 결과가 실제로 벤치마킹 아티팩트나 알려지지 않은 회귀로 인한 것인지 궁금함

* C가 "기계에 가까운" 언어가 아니라는 좋은 예임
  - clang-19가 계산된 goto 인터프리터를 "올바르게" 컴파일하지만, 최적화 의도와는 완전히 다른 출력을 생성함
  - 다른 컴파일러 버전도 "순진한" switch() 기반 인터프리터에 최적화를 적용함

* 컴파일러가 루프를 조직하는 방식을 조정하여 tail-call 인터프리터가 발표된 만큼 효과적이지 않음
  - CPU 아키텍처와 버전이 매우 중요함
  - C 추상 기계는 의도를 제대로 표현하기에 충분히 저수준이 아님
  - 특정 파라노이드 인터프리터 구현은 직접 어셈블리를 작성하는 것으로 돌아감
  - luajit는 매크로 시스템을 구현하여 효율적인 어셈블리 루프 구현을 아키텍처 간에 이식 가능하게 만듦

* Python 빌드의 성능을 평가하는 것은 매우 어려움
  - 최근 astral 팀이 conda-forge 빌드가 다른 대부분의 빌드보다 빠르다는 것을 보여줌
  - tail-call 인터프리터가 다른 빌드 최적화와 함께 어떻게 작동하는지 궁금함

* 관련 논의:
  - [Python 3.14의 새로운 기능](https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-tail-call)
  - [블로그 게시물](https://blog.reverberate.org/2025/02/10/tail-call-updates.html)

* 훌륭한 기사임
  - 참조된 기사 중 하나에서 3.14.0a5가 3.13보다 1.12배 빠르다고 언급함
  - 벤치마크를 다른 프로세스로 과부하된 상태에서 실행했는지 혼란스러움
  - 벤치마크는 외부 변수를 제거하기 위해 엄격히 통제된 환경에서 수행되어야 함

* 최근 Python 3.9에서 3.13까지 벤치마킹을 수행함
  - 3.11까지는 성능이 개선되었으나, 3.12와 3.13은 3.11보다 약 10% 느렸음
  - 자체 벤치마크가 충분하지 않다고 생각했지만, 핵심 서비스에 배포했을 때도 동일한 변화를 관찰함

* 이런 최적화가 tail-call 최적화와 어떻게 관련이 있는지 궁금함
  - 인터프리터 점프 테이블 구현이 스택 프레임 생성에 영향을 주지 않아야 함
