gpt4 book ai didi

python - For循环项目拆包

转载 作者:太空狗 更新时间:2023-10-29 21:15:29 25 4
gpt4 key购买 nike

有一次,在看完 Mike Muller 的性能优化教程(我认为是 this one )之后,一个想法开始在我脑海中浮现:如果性能很重要,请尽量减少按索引访问循环中的项目,例如。 G。如果您需要在循环中多次访问 x[1] for x in l - 将变量分配给 x[1] 并重复使用它在循环中。

现在我有了这个综合示例:

import timeit

SEQUENCE = zip(range(1000), range(1, 1001))

def no_unpacking():
return [item[0] + item[1] for item in SEQUENCE]


def unpacking():
return [a + b for a, b in SEQUENCE]


print timeit.Timer('no_unpacking()', 'from __main__ import no_unpacking').timeit(10000)
print timeit.Timer('unpacking()', 'from __main__ import unpacking').timeit(10000)

unpacking()no_unpacking() 函数返回相同的结果。实现方式不同:unpacking()在循环中将item解包为abno_unpacking() 通过索引获取值。

对于python27,它显示:

1.25280499458
0.946601867676

换句话说,unpacking()no_unpacking() 高出约 25%。

问题是:

  • 为什么按索引访问会显着降低速度(即使在这种简单的情况下)?

奖励问题:

  • 我也在 pypy 上尝试过这个 - 从性能方面来看,这两个函数之间几乎没有区别。这是为什么?

感谢您的帮助。

最佳答案

为了回答您的问题,我们可以使用 dis 检查这两个函数生成的字节码。模块:

In [5]: def no_unpacking():
...: s = []
...: for item in SEQUENCE:
...: s.append(item[0] + item[1])
...: return s
...:
...:
...: def unpacking():
...: s = []
...: for a,b in SEQUENCE:
...: s.append(a+b)
...: return s

我扩展了列表理解,因为在 python3 中检查有趣的字节码会更麻烦。代码是等效的,因此对于我们的目的来说并不重要。

第一个函数的字节码是:

In [6]: dis.dis(no_unpacking)
2 0 BUILD_LIST 0
3 STORE_FAST 0 (s)

3 6 SETUP_LOOP 39 (to 48)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 31 (to 47)
16 STORE_FAST 1 (item)

4 19 LOAD_FAST 0 (s)
22 LOAD_ATTR 1 (append)
25 LOAD_FAST 1 (item)
28 LOAD_CONST 1 (0)
31 BINARY_SUBSCR
32 LOAD_FAST 1 (item)
35 LOAD_CONST 2 (1)
38 BINARY_SUBSCR
39 BINARY_ADD
40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
43 POP_TOP
44 JUMP_ABSOLUTE 13
>> 47 POP_BLOCK

5 >> 48 LOAD_FAST 0 (s)
51 RETURN_VALUE

注意循环必须调用BINARY_SUBSCR两次访问元组的两个元素。

第二个函数的字节码是:

In [7]: dis.dis(unpacking)
9 0 BUILD_LIST 0
3 STORE_FAST 0 (s)

10 6 SETUP_LOOP 37 (to 46)
9 LOAD_GLOBAL 0 (SEQUENCE)
12 GET_ITER
>> 13 FOR_ITER 29 (to 45)
16 UNPACK_SEQUENCE 2
19 STORE_FAST 1 (a)
22 STORE_FAST 2 (b)

11 25 LOAD_FAST 0 (s)
28 LOAD_ATTR 1 (append)
31 LOAD_FAST 1 (a)
34 LOAD_FAST 2 (b)
37 BINARY_ADD
38 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
41 POP_TOP
42 JUMP_ABSOLUTE 13
>> 45 POP_BLOCK

12 >> 46 LOAD_FAST 0 (s)
49 RETURN_VALUE

注意没有BINARY_SUBSCR执行。

所以,它看起来像UNPACK_SEQUENCE加一STORE_FAST (这是解包添加的额外操作)比执行两个 BINARY_SUBSCR 更快 .这是合理的,因为 BINARY_SUBSCR是一个成熟的方法调用,而 UNPACK_SEQUENCESTORE_FAST是更简单的操作。

即使在更简单的情况下,您也可以看到差异:

In [1]: def iter_with_index(s):
...: for i in range(len(s)):
...: s[i]
...:

In [2]: def iter_without_index(s):
...: for el in s:el
...:

In [3]: %%timeit s = 'a' * 10000
...: iter_with_index(s)
...:
1000 loops, best of 3: 583 us per loop

In [4]: %%timeit s = 'a' * 10000
...: iter_without_index(s)
...:
1000 loops, best of 3: 206 us per loop

如您所见,使用显式索引对字符串进行迭代大约要慢 3 倍。这都是调用 BINARY_SUBSCR 的开销.

关于你的第二个问题:pypy 有 JIT 能够分析代码并生成一个优化版本,避免索引操作的开销。当它意识到订阅是在元组上完成时,它可能能够生成不调用元组方法但直接访问元素的代码,从而完全删除 BINARY_SUBSCR操作。

关于python - For循环项目拆包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23039485/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com