非常慢的 Numpy 缓冲区指针访问

Very slow Numpy buffer pointer access

我正在尝试获取指向 Numpy 数组的指针,以便我可以在我的 Cython 代码中快速操作它。我找到了两种获取缓冲区指针的方法,一种使用 array.__array_interface__['data'][0],另一种使用 array.ctypes.data。他们都慢得令人痛苦。

我创建了一个小型 Cython class,它只是创建一个 numpy 数组并将指针存储到其缓冲区:

cdef class ArrayHolder:
    cdef array
    cdef long *ptr

    def __init__(ArrayHolder self, allocate=True):
        self.array = np.zeros((4, 12,), dtype=np.int)
        cdef long ptr = self.array.__array_interface__['data'][0]
        self.ptr = <long *>ptr

然后,回到 Python,我创建了这个 class 的多个实例,如下所示:

for i in range(1000000):
    holder = ArrayHolder()

这大约需要 3.6 秒。使用 array.ctypes.data 是半秒 .

当我再次注释掉对 __array_instance__['data'] 和 运行 代码的调用时,它会在大约 1 秒内完成。

为什么获取Numpy数组缓冲区的地址这么慢?

我猜,这是某种延迟加载。当您第一次访问它时,Numpy 只会在 table 上执行 memset()。我会尝试创建这个数组而不用零填充它来赢得时间。

这是我的测试:

import numpy as np

cdef class ArrayHolder:
    cdef array
    cdef long *ptr

    def __init__(ArrayHolder self, allocate=True):
        self.array = np.zeros((4, 12,), dtype=np.int)

    def ptr(ArrayHolder self):
        cdef long ptr = self.array.__array_interface__['data'][0]


from timeit import timeit
from cyth import ArrayHolder


print(timeit("ArrayHolder()", number=1000000, setup="from cyth import ArrayHolder")) 
print(timeit("ArrayHolder().ptr()", number=1000000, setup="from cyth import ArrayHolder"))



$ python test.py                     
1.0442328620702028
3.4246508290525526

这可以通过使用 Cython 的静态类型机制来提供很大帮助。这样 Cython 就知道您正在处理的是适当类型的数组数组,并且可以生成优化的 C 代码。

cimport numpy as np # just so it knows np.int_t

cdef class ArrayHolder:
    cdef np.int_t[:,:] array # now specified as a specific array type
    cdef np.int_t *ptr # note I've changed this to match the array type

    def __init__(ArrayHolder self, allocate=True):
        self.array = np.zeros((4, 12,), dtype=np.int)
        self.ptr = &self.array[0,0] # location of the first element

在此版本中,分配 self.array 时会产生少量成本,以检查该对象是否实际上是一个数组。但是,元素查找和获取地址现在与使用 C 指针一样快。

在你的旧版本中它是一个任意的 python 对象,所以有一个字典查找 __array_instance__,一个字典查找 __getitem__ 允许字典查找 data__getitem__ 的进一步字典查找允许您找到索引 0.

需要注意的一件事:如果您使用 cdef 告诉 Cython 数组类型,您可以直接在数组上进行所有索引,并且它的类型速度与使用指针,因此您可以完全跳过创建指针(除非您需要它传递给外部 C 代码)。 Turn off boundscheck and wraparound 最后一点点速度。