没有 numpy 临时对象?这是怎么做到的?

No numpy temporary objects? How is this done?

我是 运行 Cython 函数中的以下代码:

a = np.zeros((3,3,))
b = np.ones((3,3,))

for i in range(1000000):
    a += b * i

return a

在此代码中,只有两个 numpy 数组分配。我希望其中有 1,000,002 个。当我用自己的 class 替换 numpy 数组时,我看到它的 __mul__ 函数被调用了 1,000,000 次,导致 1,000,000 次对象分配。

numpy 怎么知道它不需要为每次迭代分配临时对象来存储 b * i?

如果你看一下 cython 生成的代码,我认为关键行是

__pyx_t_1 = PyNumber_Multiply(__pyx_v_b, __pyx_v_i); // some error checking follows

PyNumber_Multiplythe standard c-api function to call the multiplication operator 所以没有理由相信它的行为方式与普通的乘法调用不同。但是,我们可以很容易地检查中间体的类型……我拿了编译后的 C 文件并插入了行

if (__Pyx_PrintOne(0, ((PyObject *)Py_TYPE(__pyx_t_1))) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 11; __pyx_clineno = __LINE__; goto __pyx_L1_error;}

(这个只是看print(type(a))生成的代码,改了一个变量名得到的,注意我没有改Python的代码,所以我不信这会生成一个以前不存在的变量)。然后我手动编译了文件(gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \ -I/usr/include/python3.4m -o filename.so filename.c on linux)。

这会打印

<class 'numpy.ndarray'>

这确实表明它已经生成了一个普通的 numpy 数组对象作为临时对象,正如您所期望的那样。没有魔法发生。


作为一个有趣的旁注,我相信 Numba 的最新版本实际上可以消除临时性并真正地完成整个操作。然而,实际上证明你给出的例子会发生这种情况有点超出我的范围(你可以通过在生成的函数上调用 inspect_asm() 来看到它,并注意到有很多 add/multiply 指令,但不是明显的函数调用)。