[英]Cython: are typed memoryviews the modern way to type numpy arrays?
假設我想將一個numpy數組傳遞給cdef
函數:
cdef double mysum(double[:] arr):
cdef int n = len(arr)
cdef double result = 0
for i in range(n):
result = result + arr[i]
return result
這是處理鍵入numpy數組的現代方法嗎? 與這個問題相比: cython / numpy類型的數組
如果我想執行以下操作該怎么辦?
cdef double[:] mydifference(int a, int b):
cdef double[:] arr_a = np.arange(a)
cdef double[:] arr_b = np.arange(b)
return arr_a - arr_b
這將返回錯誤,因為-
沒有為memoryviews定義。 那么,該案件是否應如下處理?
cdef double[:] mydifference(int a, int b):
arr_a = np.arange(a)
arr_b = np.arange(b)
return arr_a - arr_b
我將引用文檔中的文檔
Memoryview類似於當前的NumPy數組緩沖支持(
np.ndarray[np.float64_t, ndim=2]
),但它們具有更多功能和更清晰的語法。
這表明Cython的開發人員認為內存視圖是現代方式。
內存視圖提供了一些優於np.ndarray
表示法的優點,主要表現在優雅和互操作性,但它們的性能並不優越。
首先應該注意的是,boundscheck 有時無法使用內存視圖導致內存視圖的人為快速數據,其中boundscheck = True(即你得到快速,不安全的索引),如果你依靠boundscheck來捕獲bug,這可能是一個令人討厭的驚喜。
在大多數情況下,一旦應用了編譯器優化,內存視圖和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數組,那么為了保持一致性,使用內存視圖是非常合理的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.