我将引用文档the docs
Memoryviews 类似于当前的 NumPy 数组缓冲区支持 (np.ndarray[np.float64_t, ndim=2]),但它们具有更多功能和更简洁的语法。
这表明 Cython 的开发人员认为内存视图是现代方式。
与np.ndarray 表示法相比,内存视图提供了一些很大的优势,主要是在优雅和互操作性方面,但它们在性能上并不优越。
性能:
首先应该注意,boundscheck 有时无法与内存视图一起工作,从而导致具有 boundscheck=True 的内存视图的人为快速数字(即,如果您依赖于快速、不安全的索引)在 boundscheck 上捕获错误,这可能是一个令人讨厌的惊喜。
在大多数情况下,一旦应用了编译器优化,内存视图和 numpy 数组表示法在性能上是相等的,通常情况下确实如此。当有差异时,通常不超过 10-30%。
性能基准
数字是执行 100,000,000 次操作的时间(以秒为单位)。越小越快。
ACCESS+ASSIGNMENT on small array (10000 elements, 10000 times)
Results for `uint8`
1) memory view: 0.0415 +/- 0.0017
2) np.ndarray : 0.0531 +/- 0.0012
3) pointer : 0.0333 +/- 0.0017
Results for `uint16`
1) memory view: 0.0479 +/- 0.0032
2) np.ndarray : 0.0480 +/- 0.0034
3) pointer : 0.0329 +/- 0.0008
Results for `uint32`
1) memory view: 0.0499 +/- 0.0021
2) np.ndarray : 0.0413 +/- 0.0005
3) pointer : 0.0332 +/- 0.0010
Results for `uint64`
1) memory view: 0.0489 +/- 0.0019
2) np.ndarray : 0.0417 +/- 0.0010
3) pointer : 0.0353 +/- 0.0017
Results for `float32`
1) memory view: 0.0398 +/- 0.0027
2) np.ndarray : 0.0418 +/- 0.0019
3) pointer : 0.0330 +/- 0.0006
Results for `float64`
1) memory view: 0.0439 +/- 0.0037
2) np.ndarray : 0.0422 +/- 0.0013
3) pointer : 0.0353 +/- 0.0013
ACCESS PERFORMANCE (100,000,000 element array):
Results for `uint8`
1) memory view: 0.0576 +/- 0.0006
2) np.ndarray : 0.0570 +/- 0.0009
3) pointer : 0.0061 +/- 0.0004
Results for `uint16`
1) memory view: 0.0806 +/- 0.0002
2) np.ndarray : 0.0882 +/- 0.0005
3) pointer : 0.0121 +/- 0.0003
Results for `uint32`
1) memory view: 0.0572 +/- 0.0016
2) np.ndarray : 0.0571 +/- 0.0021
3) pointer : 0.0248 +/- 0.0008
Results for `uint64`
1) memory view: 0.0618 +/- 0.0007
2) np.ndarray : 0.0621 +/- 0.0014
3) pointer : 0.0481 +/- 0.0006
Results for `float32`
1) memory view: 0.0945 +/- 0.0013
2) np.ndarray : 0.0947 +/- 0.0018
3) pointer : 0.0942 +/- 0.0020
Results for `float64`
1) memory view: 0.0981 +/- 0.0026
2) np.ndarray : 0.0982 +/- 0.0026
3) pointer : 0.0968 +/- 0.0016
ASSIGNMENT PERFORMANCE (100,000,000 element array):
Results for `uint8`
1) memory view: 0.0341 +/- 0.0010
2) np.ndarray : 0.0476 +/- 0.0007
3) pointer : 0.0402 +/- 0.0001
Results for `uint16`
1) memory view: 0.0368 +/- 0.0020
2) np.ndarray : 0.0368 +/- 0.0019
3) pointer : 0.0279 +/- 0.0009
Results for `uint32`
1) memory view: 0.0429 +/- 0.0022
2) np.ndarray : 0.0427 +/- 0.0005
3) pointer : 0.0418 +/- 0.0007
Results for `uint64`
1) memory view: 0.0833 +/- 0.0004
2) np.ndarray : 0.0835 +/- 0.0011
3) pointer : 0.0832 +/- 0.0003
Results for `float32`
1) memory view: 0.0648 +/- 0.0061
2) np.ndarray : 0.0644 +/- 0.0044
3) pointer : 0.0639 +/- 0.0005
Results for `float64`
1) memory view: 0.0854 +/- 0.0056
2) np.ndarray : 0.0849 +/- 0.0043
3) pointer : 0.0847 +/- 0.0056
基准代码(仅针对访问+分配显示)
# cython: boundscheck=False
# cython: wraparound=False
# cython: nonecheck=False
import numpy as np
cimport numpy as np
cimport cython
# Change these as desired.
data_type = np.uint64
ctypedef np.uint64_t data_type_t
cpdef test_memory_view(data_type_t [:] view):
cdef Py_ssize_t i, j, n = view.shape[0]
for j in range(0, n):
for i in range(0, n):
view[i] = view[j]
cpdef test_ndarray(np.ndarray[data_type_t, ndim=1] view):
cdef Py_ssize_t i, j, n = view.shape[0]
for j in range(0, n):
for i in range(0, n):
view[i] = view[j]
cpdef test_pointer(data_type_t [:] view):
cdef Py_ssize_t i, j, n = view.shape[0]
cdef data_type_t * data_ptr = &view[0]
for j in range(0, n):
for i in range(0, n):
(data_ptr + i)[0] = (data_ptr + j)[0]
def run_test():
import time
from statistics import stdev, mean
n = 10000
repeats = 100
a = np.arange(0, n, dtype=data_type)
funcs = [('1) memory view', test_memory_view),
('2) np.ndarray', test_ndarray),
('3) pointer', test_pointer)]
results = {label: [] for label, func in funcs}
for r in range(0, repeats):
for label, func in funcs:
start=time.time()
func(a)
results[label].append(time.time() - start)
print('Results for `{}`'.format(data_type.__name__))
for label, times in sorted(results.items()):
print('{: <14}: {:.4f} +/- {:.4f}'.format(label, mean(times), stdev(times)))
这些基准表明,总体而言,性能差异不大。有时 np.ndarray 表示法更快一点,有时反之亦然。
使用基准测试需要注意的一点是,当代码变得更复杂或“现实”一点时,差异会突然消失,就好像编译器失去了应用一些非常聪明的优化的信心。这可以从浮点数的性能中看出,其中没有任何差异,大概是因为无法使用一些花哨的整数优化。
易于使用
内存视图具有显着优势,例如,您可以在 numpy 数组、CPython 数组、cython 数组、c 数组等上使用内存视图,无论是现在还是未来。还有一种简单的并行语法可以将任何东西投射到内存视图中:
cdef double [:, :] data_view = <double[:256, :256]>data
内存视图在这方面很棒,因为如果您键入一个函数作为采用内存视图,那么它可以采用任何这些东西。这意味着你可以编写一个不依赖于 numpy 的模块,但它仍然可以使用 numpy 数组。
另一方面,np.ndarray 表示法导致的结果仍然是一个 numpy 数组,您可以在其上调用所有 numpy 数组方法。不过,同时拥有一个 numpy 数组和一个数组视图并不是什么大问题:
def dostuff(arr):
cdef double [:] arr_view = arr
# Now you can use 'arr' if you want array functions,
# and arr_view if you want fast indexing
在实践中,数组和数组视图都可以正常工作,我非常喜欢这种风格,因为它清楚地区分了 python 级方法和 c 级方法。
结论
性能几乎相等,当然没有足够的差异作为决定因素。
numpy 数组表示法更接近于加速 Python 代码而不对其进行太多更改的理想状态,因为您可以继续使用相同的变量,同时获得全速数组索引。
另一方面,内存视图符号可能是未来。如果您喜欢它的优雅,并且使用不同类型的数据容器而不仅仅是 numpy 数组,那么出于一致性考虑,有很好的理由使用内存视图。