簡體   English   中英

Cython:類型化內存視圖是鍵入numpy數組的現代方法嗎?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM