【发布时间】:2021-11-03 19:37:11
【问题描述】:
短版:如果s 是一个字符串,那么s = s + 'c' 可能会修改字符串,而t = s + 'c' 不能。但是s + 'c'这个操作怎么知道是在哪个场景呢?
加长版:
t = s + 'c' 需要创建一个单独的字符串,因为之后程序希望旧字符串为s,新字符串为t。
如果s 是唯一的引用,s = s + 'c' 可以就地修改字符串,因为程序只希望s 成为扩展字符串。如果末尾有多余的字符空间,CPython 实际上会进行此优化。
考虑这些函数,它们会重复添加一个字符:
def fast(n):
s = ''
for _ in range(n):
s = s + 'c'
t = s
del t
def slow(n):
s = ''
for _ in range(n):
t = s + 'c'
s = t
del t
使用n = 100_000 (Try it online!) 对结果进行基准测试:
fast : 9 ms 9 ms 9 ms 9 ms 10 ms
slow : 924 ms 927 ms 931 ms 933 ms 945 ms
请注意,额外的t = s 或s = t 使这两个变量等效引用字符串,然后del t 只留下s,因此在下一次循环迭代中,s 再次成为对字符串的唯一引用细绳。因此,这两个函数之间的唯一区别是s + 'c' 分配给s 和t 的顺序。
让我们也反汇编字节码。我在中间用!= 标记了仅有的三个区别。正如预期的那样,只有STORE_FAST 和LOAD_FAST 的变量不同。但直到并包括BINARY_ADD,字节码是相同的。 那么BINARY_ADD 是如何知道是否优化的呢?
import dis import dis
dis.dis(fast) dis.dis(slow)
---------------------------------------------------------------------------
0 LOAD_CONST 1 ('') 0 LOAD_CONST 1 ('')
2 STORE_FAST 1 (s) 2 STORE_FAST 1 (s)
4 LOAD_GLOBAL 0 (range) 4 LOAD_GLOBAL 0 (range)
6 LOAD_FAST 0 (n) 6 LOAD_FAST 0 (n)
8 CALL_FUNCTION 1 8 CALL_FUNCTION 1
10 GET_ITER 10 GET_ITER
>> 12 FOR_ITER 18 (to 32) >> 12 FOR_ITER 18 (to 32)
14 STORE_FAST 2 (_) 14 STORE_FAST 2 (_)
16 LOAD_FAST 1 (s) 16 LOAD_FAST 1 (s)
18 LOAD_CONST 2 ('c') 18 LOAD_CONST 2 ('c')
20 BINARY_ADD 20 BINARY_ADD
22 STORE_FAST 1 (s) != 22 STORE_FAST 3 (t)
24 LOAD_FAST 1 (s) != 24 LOAD_FAST 3 (t)
26 STORE_FAST 3 (t) != 26 STORE_FAST 1 (s)
28 DELETE_FAST 3 (t) 28 DELETE_FAST 3 (t)
30 JUMP_ABSOLUTE 12 30 JUMP_ABSOLUTE 12
>> 32 LOAD_CONST 0 (None) >> 32 LOAD_CONST 0 (None)
34 RETURN_VALUE 34 RETURN_VALUE
【问题讨论】:
-
我的猜测是在 Python 字节码组装完成后 会发生一些 JIT 优化。一些类似于 LLVM 的低级系统正在查看字节码,决定可以做些什么,并在编译后或运行前透明地进行。
-
Binary_add 不知道,它调用的 c 代码执行了优化,因为它看到了引用计数(因此知道它是安全的)
-
可能值得将其限制为特定版本。即将进行一些非常重要的优化(针对特定内置类型的操作码的专门化已经在 CPython 存储库中),但它们不适用于您为此计时的版本 - 坦率地说,在查看 3.8 源代码之后这似乎确实令人惊讶(也很有趣!)。
-
@ead 但是那个 C 代码不是在两个版本中看到相同的引用计数吗?
-
魔法发生在
string_concatenate()(在 Python/ceval.c 中),它又被调用为两个字符串的 BINARY_ADD。它向前查看下一个字节码指令,并且仅当该指令是加法的第一个操作数的 STORE 时才考虑这种就地修改。
标签: python performance cpython internals