为什么 {1,2} 比 {1,2,3} 更慢?

编程语言 2026-07-09

对每个进行几次基准测试:

{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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章