python / python/cpython

Optimize collection literals with leading null unpack idiom in bytecode, allows bare `BUILD_SET 0`

未關閉
#150,737 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

interpreter-core type-feature
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

Feature or enhancement

Proposal:

Performance was discussed at various times within PEP 802 topic, submitting this proposal as the PEP is unlikely to be accepted. This is intended as an advanced escape-hatch not overlapping with the main reasons for PEP 802.

Goal:

  • Remove performance penalty of ast.unparse(ast.Set(elts=[])) ({*()} can be used in advanced cases and will not change recommended way to write empty set set())
  • Remove performance penalty of incremental construction between 0-to-1/1-to-0 elements for set and tuple literals without special case considerations (e.g. x = *(), pre-seeded tuple literal without trailing comma pitfall)
  • Keep implementation simple/self-contained in bytecode generation, avoiding any AST changes to language
  • Near-zero performance impact to bytecode generation (improves subsequent bytecode optimizations of target case)

Implementation examples:

Micro-benchmark

Run on windows x64bit release build: empty_unpack_benchmark.py

Compile time

Targeted cases:

Benchmark Main Flowgraph only Codegen leading only
[*()] 1.450 ms / 1.000x 1.393 ms / 0.961x 1.378 ms / 0.950x
{*()} 1.530 ms / 1.000x 1.488 ms / 0.973x 1.467 ms / 0.959x
(*(),) 1.916 ms / 1.000x 1.842 ms / 0.962x 1.832 ms / 0.956x
x = *(), 1.139 ms / 1.000x 1.088 ms / 0.955x 1.055 ms / 0.927x
xychart-beta
	title "Compile targeted mean ratio vs main"
	x-axis [Main, Flowgraph, Leading]
	y-axis "Ratio" 0 --> 1.01
	bar [1.000, 0.963, 0.948]

Control near-miss case comparisons moved less than +/-1% and are attributed to noise.

Runtime

The set() versus {*()} comparison is especially useful because it changes qualitatively across the two branches (~6.3% slower to ~29.5% faster):

Branch set() mean (ns) {*()} mean (ns) {*()} vs set()
Main 54.14 57.53 1.063x
Flowgraph only 53.49 38.01 0.711x
Codegen leading only 55.44 39.00 0.704x

Before this change, {*()} was paying for a redundant empty update and ended up slower than the constructor call. After the change, {*()} compiles down to a direct BUILD_SET 0; RETURN_VALUE path, while set() still has to load the global and perform a zero-argument call.

Has this already been discussed elsewhere?

I have already discussed this feature proposal on Discourse

Links to previous discussion of this feature:

https://discuss.python.org/t/pep-802-display-syntax-for-the-empty-set/101676/237
https://discuss.python.org/t/pep-802-display-syntax-for-the-empty-set/101676/216
https://discuss.python.org/t/pep-802-display-syntax-for-the-empty-set/101676/3

Linked PRs
  • gh-150812

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

先查看 empty_unpack_benchmark.py 和提案中連結的實作範例,接著檢查連結的 PR gh-150812 以及相關的 Discourse 討論。當提出的集合字面值案例能避免多餘的空更新,同時保留文件記載的行為和基準測試改進時,即視為完成。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
compilers
Issue 類型
功能
難度
5/5
預估耗時
一週以上
活躍度
停滯
描述清晰度
基本清楚
新手友好度
25/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。