我写了两个简单的函数来学习CPython关于numpy数组的行为。
Python 3.12.1和numpy版本1.26.2,由mkl编译(conda默认)
def foo():
for i in range(100):
H = np.random.rand(1000, 1000)
%timeit -r 100 foo()
个字符
使用dis库通过调用dis.dis(foo())和dis.dis(baaz())查看字节码,我得到这两个输出。
最初,我认为baaz()应该比foo()运行得更快,因为我们重用了H数组,而不是在每个循环中重新分配H。然而,我一直看到foo()更快。我想知道这是什么原因。我无法阅读汇编字节码,但通过简单地查看dis.dis(foo())和dis.dis(baaz())输出,我可以看到foo()比baaz()多生成了13行代码。
Dissembled foo():
671 0 RETURN_GENERATOR
2 POP_TOP
4 RESUME 0
676 6 LOAD_CONST 1 (None)
8 STORE_FAST 1 (lastline)
677 10 LOAD_FAST 0 (code)
--> 12 LOAD_ATTR 1 (NULL|self + co_lines)
32 CALL 0
40 GET_ITER
>> 42 FOR_ITER 23 (to 92)
46 UNPACK_SEQUENCE 3
50 STORE_FAST 2 (start)
52 STORE_FAST 3 (end)
54 STORE_FAST 4 (line)
678 56 LOAD_FAST 4 (line)
58 POP_JUMP_IF_NOT_NONE 1 (to 62)
60 JUMP_BACKWARD 10 (to 42)
>> 62 LOAD_FAST 4 (line)
64 LOAD_FAST 1 (lastline)
66 COMPARE_OP 55 (!=)
70 POP_JUMP_IF_TRUE 1 (to 74)
72 JUMP_BACKWARD 16 (to 42)
679 >> 74 LOAD_FAST 4 (line)
76 STORE_FAST 1 (lastline)
680 78 LOAD_FAST 2 (start)
80 LOAD_FAST 4 (line)
82 BUILD_TUPLE 2
84 YIELD_VALUE 1
86 RESUME 1
88 POP_TOP
90 JUMP_BACKWARD 25 (to 42)
677 >> 92 END_FOR
681 94 RETURN_CONST 1 (None)
>> 96 CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
98 RERAISE 1
ExceptionTable:
4 to 58 -> 96 [0] lasti
62 to 70 -> 96 [0] lasti
74 to 94 -> 96 [0] lasti
型
Dissembled baaz():
P.S:为什么人们会认为baaz()应该更快,这似乎并不明显,但在Julia Understanding Julia multi-thread / multi-process design这样的语言中确实如此。
1条答案
按热度按时间pvabu6sv1#
在这两种情况下,当你执行
np.random.rand(1000, 1000)
的时候,你都在创建一个新的数组,然后取消分配它。在baaz
的情况下,你还在更新初始数组。因此它会慢一些。Numpy函数提供了一种避免这种情况的方法,考虑一个简单的情况:
字符串
这 always 创建了一个新数组,它是表达式
arr + 1
的结果。你可以通过使用来避免这种情况:型
上面的一个简单的例子:
型
不幸的是,我不认为
numpy.random
函数有任何等价的东西。也许,numba
可以帮助你,虽然不确定np.random
有多优化。但值得一看。