从解释器的Angular 来看,numpy数组是如何被覆盖的?

rxztt3cl  于 11个月前  发布在  Angular
关注(0)|答案(1)|浏览(107)

我写了两个简单的函数来学习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这样的语言中确实如此。

pvabu6sv

pvabu6sv1#

在这两种情况下,当你执行np.random.rand(1000, 1000)的时候,你都在创建一个新的数组,然后取消分配它。在baaz的情况下,你还在更新初始数组。因此它会慢一些。
Numpy函数提供了一种避免这种情况的方法,考虑一个简单的情况:

arr[:] = arr + 1

字符串
always 创建了一个新数组,它是表达式arr + 1的结果。你可以通过使用来避免这种情况:

np.add(arr, 1, out=arr)


上面的一个简单的例子:

In [31]: %%timeit -r 100 arr = np.zeros(1_000_000)
    ...: arr[:] = arr + 1
    ...:
    ...:
1.85 ms ± 375 µs per loop (mean ± std. dev. of 100 runs, 100 loops each)

In [32]: %%timeit -r 100 arr = np.zeros(1_000_000)
    ...: np.add(arr, 1, out=arr)
    ...:
    ...:
418 µs ± 29.1 µs per loop (mean ± std. dev. of 100 runs, 1,000 loops each)


不幸的是,我不认为numpy.random函数有任何等价的东西。也许,numba可以帮助你,虽然不确定np.random有多优化。但值得一看。

相关问题