为什么 {1,2} 比 {1,2,3} 更慢?
对每个进行几次基准测试:
{1,2} 86.4 ns
{1,2,3} 79.6 ns
{1,2} 86.7 ns
{1,2,3} 79.7 ns
{1,2} 85.9 ns
{1,2,3} 80.1 ns
Python: 3.13.0 (main, Nov 9 2025, 11:53:23) [GCC 15.2.1 20250813]
为什么 {1,2} slower 比 {1,2,3} 慢?{1,2,3} 不也应该做同样的事情,and something extra?
基准测试脚本(Attempt This Online!):
import timeit, sys
for s in ['{1,2}', '{1,2,3}'] * 3:
t = min(timeit.repeat(s)) * 1e3
print(f'{s:7} {t:4.1f} ns')
print('\nPython:', sys.version)
解决方案
生成的字节码在包含3 个及以上字面量时不同(请见下方的编辑,说明出处)
from dis import dis
def f():
{1}
{2, 3}
{4, 5, 6}
dis(f)
输出:
2 0 RESUME 0
3 2 LOAD_CONST 1 (1)
4 BUILD_SET 1
6 POP_TOP
4 8 LOAD_CONST 2 (2)
10 LOAD_CONST 3 (3)
12 BUILD_SET 2
14 POP_TOP
5 16 BUILD_SET 0
18 LOAD_CONST 4 (frozenset({4, 5, 6}))
20 SET_UPDATE 1
22 POP_TOP
24 LOAD_CONST 0 (None)
26 RETURN_VALUE
对于1 个或2 个字面量,值会逐个加载,然后再从它们构造集合。
对于3 个或以上,先创建一个空集合,然后一次性用一个(在解析阶段预构建的,我猜?)frozenset来更新它。(列表同理,使用一个预构建的元组。)
这也解释了为什么对像 {1, 1, ... 1} 这样更长的集合,其速度仍然保持不变,因为在每种情况下,frozenset就只是 {1}。
这也解释了为什么一旦集合元素中出现变量,我们就不再看到2 个元素和3 个元素之间的差异:不会创建frozenset,值和变量逐一加载,最后在末尾创建集合,就像2 个字面量的情况一样。所需的时间随后会随着元素数量的增加而规律地增长。
def f():
x = 1
{3, 4, 5, 6, 7, x}
4 6 LOAD_CONST 2 (3)
8 LOAD_CONST 3 (4)
10 LOAD_CONST 4 (5)
12 LOAD_CONST 5 (6)
14 LOAD_CONST 6 (7)
16 LOAD_FAST 0 (x)
18 BUILD_SET 6
20 POP_TOP
一个很好的问题是:当只有两个字面量时,是否也应该使用预先构建好的frozensets/元组来提速?
我在源码中搜索了这个现象发生的位置:
在 cpython/Python/flowgraph.c, 我们看到了对该优化的描述:
#define MIN_CONST_SEQUENCE_SIZE 3
/*
Optimize lists and sets for:
1. "for" loop, comprehension or "in"/"not in" tests:
Change literal list or set of constants into constant
tuple or frozenset respectively. Change list of
non-constants into tuple.
2. Constant literal lists/set with length >= MIN_CONST_SEQUENCE_SIZE:
Replace LOAD_CONST c1, LOAD_CONST c2 ... LOAD_CONST cN, BUILD_LIST N
with BUILD_LIST 0, LOAD_CONST (c1, c2, ... cN), LIST_EXTEND 1,
or BUILD_SET & SET_UPDATE respectively.
*/
MIN_CONST_SEQUENCE_SIZE 已在 此提交 中引入,但我在代码的最早版本中尚未发现这样的最小值的痕迹(说不定我漏看了),也找不到关于这个看似任意数值的讨论。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。