我有一些代码只需要对2D numpy数组的行的子集运行计算。例如,如果数组说:
[[0.5, 0.5, 0.5]
[0.9, 0.2, 0.8]
[0.9, 0.2, 0.8],
[0.9, 0.2, 0.2]]
我可能只想使用第一行和第三行。如果要排除大多数行,遍历数组比进行矢量化计算要快。
Cython可以加快迭代速度,但是一些实验似乎表明,使用较新的“ memoryviews” API会导致大量额外的分配(我认为是内存视图包装对象?),这使其速度比旧的np慢得多。 .ndarray API,在某些情况下会比普通Python慢。
这是一个最小的例子,说明了这一点:
在文件my_cython.pyx中:
# cython: profile=True
import numpy as np
cimport numpy as np
def cython_test1(
np.ndarray vectors,
np.ndarray target_vector):
cdef Py_ssize_t i
for i in range(1000000):
x = np.dot(target_vector, vectors[i])
def cython_test2(
np.float32_t[:,:] vectors,
np.float32_t[:] target_vector):
cdef Py_ssize_t i
for i in range(1000000):
x = np.dot(target_vector, vectors[i])
从ipython REPL进行基准测试:
from my_cython import cython_test1, cython_test2
import numpy as np
vectors = np.random.random((1000000, 100)).astype(np.float32)
target_vector = np.random.random(100).astype(np.float32)
%timeit cython_test1(vectors, target_vector)
%timeit cython_test2(vectors, target_vector)
cython_test1: 462 ms ± 2.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cython_test2: 1.23 s ± 8.82 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
这是显示问题的简化示例,我知道在这种情况下,在numpy数组的每个单个元素上依次运行点积显然是没有意义的。
在附加了探查器的情况下运行:
import pstats, cProfile
cProfile.runctx("cython_test2(vectors, target_vector)", globals(), locals(), "Profile.prof")
s = pstats.Stats("Profile.prof")
s.strip_dirs().sort_stats("time").print_stats()
结果:
1 2.070 2.070 3.263 3.263 speedy.pyx:52(cython_test2)
2000000 0.705 0.000 0.867 0.000 stringsource:995(memoryview_fromslice)
4000000 0.155 0.000 0.155 0.000 stringsource:514(__getbuffer__)
2000002 0.090 0.000 0.090 0.000 stringsource:372(__dealloc__)
2000002 0.082 0.000 0.082 0.000 stringsource:345(__cinit__)
2000000 0.081 0.000 0.081 0.000 stringsource:972(__dealloc__)
2000000 0.080 0.000 0.080 0.000 stringsource:555(__get__)
...
也许它正在为从2D矩阵访问的每个1D向量创建一个新的切片视图?如果是这样,答案可能是“在此特定实例中不要使用内存视图”。但这与文档有关,后者表示更新的API速度更快,并表明它总是更可取。