提升Python + numpy数组分配/初始化性能

0 投票
1 回答
1732 浏览
提问于 2025-04-18 12:00

我正在写一个Python程序,使用一些来自DLL的外部功能。我的问题是如何在C代码和Python中的矩阵(numpy数组)之间传递数据。目前我使用以下代码从DLL接收数据:

peak_count = ct.c_int16()
peak_wl_array = np.zeros(512, dtype=np.double)
peak_pwr_array = np.zeros(512, dtype=np.double)

res = __dll.DLL_Search_Peaks(ctypes.c_int(data.shape[0])
                             ctypes.c_void_p(data_array.ctypes.data),
                             ctypes.c_void_p(peak_wl_array.ctypes.data),
                             ctypes.c_void_p(peak_pwr_array.ctypes.data),
                             ct.byref(peak_count))

这个方法运行得很好,但我的问题是numpy的分配速度——即使不调用DLL(只是注释掉),我每100,000次调用也要花3.1秒

我只是用np.zeros()来分配内存,然后用ctypes.c_void_p(D.ctypes.data)获取可写指针。

我需要每秒处理大约20,000次调用,所以几乎所有的时间都花在了分配内存上。

我考虑过使用cython,但它并不能加速numpy数组,所以我不会有什么收益。

有没有更快的方法从C编写的DLL接收类似矩阵的数据呢?

1 个回答

2

内存操作是比较耗费资源的,不管是用numpy还是其他方式。

如果你需要分配很多数组,最好是只分配一次,然后用视图或者子数组来只使用数组的一部分,这样会更高效:

import numpy as np

niters=10000
asize=512

def forig():
    for i in xrange(niters):
        peak_wl_array = np.empty((asize), dtype=np.double)
        peak_pwr_array = np.empty((asize), dtype=np.double)

    return peak_pwr_array


def fviews():
    peak_wl_arrays  = np.empty((asize*niters), dtype=np.double)
    peak_pwr_arrays = np.empty((asize*niters), dtype=np.double)

    for i in xrange(niters):
        # create views
        peak_wl_array  = peak_wl_arrays[i*asize:(i+1)*asize]
        peak_pwr_array = peak_pwr_arrays[i*asize:(i+1)*asize]
        # then do something

    return peak_pwr_emptys


def fsubemptys():
    peak_wl_arrays  = np.empty((niters,asize), dtype=np.double)
    peak_pwr_arrays = np.empty((niters,asize), dtype=np.double)

    for i in xrange(niters):
        # do something with peak_wl_arrays[i,:]

    return peak_pwr_emptys


import timeit

print timeit.timeit(forig,number=100)
print timeit.timeit(fviews,number=100)
print timeit.timeit(fsubemptys,number=100)

运行结果是

3.41996979713
0.844147920609
0.00169682502747

注意,如果你使用的是np.zeros这样的函数,那么你大部分时间是在初始化内存,而不是分配内存,这个过程会花费更多时间,几乎会消除这两种方法之间的差别:

4.20200014114
5.43090081215
4.58127593994

在较新的系统上,单线程访问主内存的速度大约是10GB/s(每秒1亿个双精度浮点数),所以每次调用大约需要

1024个双精度浮点数/次调用 / (1亿个双精度浮点数/秒) ~ 1微秒/次调用

来清空内存,这已经占用了你看到的执行时间的相当一部分。不过,如果你在进行调用之前先初始化一个大的数组,那么总的执行时间会保持不变,但每次调用的延迟会更低。

撰写回答