web-dev-qa-db-ja.com

型付きメモリビューにメモリを割り当てる推奨される方法は何ですか?

型付きメモリビューに関するCythonのドキュメント は、型付きメモリビューに割り当てる3つの方法を示しています。

  1. 生のCポインタから、
  2. np.ndarrayから
  3. cython.view.arrayから。

外部からcython関数にデータを渡さず、代わりにメモリを割り当ててnp.ndarrayとして返したいと仮定します。これらのオプションのどれを選択しましたか?また、そのバッファーのサイズがコンパイル時の定数ではない、つまりスタックに割り当てることはできませんが、オプション1の場合はmallocが必要だと仮定します。

したがって、3つのオプションは次のようになります。

from libc.stdlib cimport malloc, free
cimport numpy as np
from cython cimport view

np.import_array()

def memview_malloc(int N):
    cdef int * m = <int *>malloc(N * sizeof(int))
    cdef int[::1] b = <int[:N]>m
    free(<void *>m)

def memview_ndarray(int N):
    cdef int[::1] b = np.empty(N, dtype=np.int32)

def memview_cyarray(int N):
    cdef int[::1] b = view.array(shape=(N,), itemsize=sizeof(int), format="i")

私にとって驚くべきことは、3つすべてのケースで、 Cythonは非常に多くのコードを生成します メモリ割り当て、特に__Pyx_PyObject_to_MemoryviewSlice_dc_intの呼び出しです。これは、最初にPythonオブジェクトを作成し、次にそれをメモリビューに「キャスト」することを示唆しています(ここで間違っている可能性があります。Cythonの内部動作に対する私の洞察は非常に限られています)不要なオーバーヘッドのようです。

単純なベンチマーク では、3つの方法の間に大きな違いはありません。2。はわずかなマージンで最速です。

推奨される3つの方法はどれですか?または、別のより良いオプションがありますか?

次の質問:関数でそのメモリビューを操作した後、最終的にnp.ndarrayとして結果を返します。型付きメモリビューが最良の選択ですか、それとも最初の場所でndarrayを作成するために以下のように古いバッファインターフェイスを使用するだけですか?

cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32)
64
kynan

こちら で答えを探してください。

基本的な考え方は、cpython.array.arrayおよびcpython.array.clonenotcython.array.*):

from cpython.array cimport array, clone

# This type is what you want and can be cast to things of
# the "double[:]" syntax, so no problems there
cdef array[double] armv, templatemv

templatemv = array('d')

# This is fast
armv = clone(templatemv, L, False)

[〜#〜] edit [〜#〜]

そのスレッドのベンチマークはゴミだったことがわかりました。私のタイミングと私のセットは次のとおりです。

# cython: language_level=3
# cython: boundscheck=False
# cython: wraparound=False

import time
import sys

from cpython.array cimport array, clone
from cython.view cimport array as cvarray
from libc.stdlib cimport malloc, free
import numpy as numpy
cimport numpy as numpy

cdef int loops

def timefunc(name):
    def timedecorator(f):
        cdef int L, i

        print("Running", name)
        for L in [1, 10, 100, 1000, 10000, 100000, 1000000]:
            start = time.clock()
            f(L)
            end = time.clock()
            print(format((end-start) / loops * 1e6, "2f"), end=" ")
            sys.stdout.flush()

        print("μs")
    return timedecorator

print()
print("INITIALISATIONS")
loops = 100000

@timefunc("cpython.array buffer")
def _(int L):
    cdef int i
    cdef array[double] arr, template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cpython.array memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr
    cdef array template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cpython.array raw C type")
def _(int L):
    cdef int i
    cdef array arr, template = array('d')

    for i in range(loops):
        arr = clone(template, L, False)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("numpy.empty_like memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr
    template = numpy.empty((L,), dtype='double')

    for i in range(loops):
        arr = numpy.empty_like(template)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("malloc")
def _(int L):
    cdef int i
    cdef double* arrptr

    for i in range(loops):
        arrptr = <double*> malloc(sizeof(double) * L)
        free(arrptr)

    # Prevents dead code elimination
    str(arrptr[0])

@timefunc("malloc memoryview")
def _(int L):
    cdef int i
    cdef double* arrptr
    cdef double[::1] arr

    for i in range(loops):
        arrptr = <double*> malloc(sizeof(double) * L)
        arr = <double[:L]>arrptr
        free(arrptr)

    # Prevents dead code elimination
    str(arr[0])

@timefunc("cvarray memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr

    for i in range(loops):
        arr = cvarray((L,),sizeof(double),'d')

    # Prevents dead code elimination
    str(arr[0])



print()
print("ITERATING")
loops = 1000

@timefunc("cpython.array buffer")
def _(int L):
    cdef int i
    cdef array[double] arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("cpython.array memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("cpython.array raw C type")
def _(int L):
    cdef int i
    cdef array arr = clone(array('d'), L, False)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("numpy.empty_like memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = numpy.empty((L,), dtype='double')

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

@timefunc("malloc")
def _(int L):
    cdef int i
    cdef double* arrptr = <double*> malloc(sizeof(double) * L)

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arrptr[i]

    free(arrptr)

    # Prevents dead-code elimination
    str(d)

@timefunc("malloc memoryview")
def _(int L):
    cdef int i
    cdef double* arrptr = <double*> malloc(sizeof(double) * L)
    cdef double[::1] arr = <double[:L]>arrptr

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    free(arrptr)

    # Prevents dead-code elimination
    str(d)

@timefunc("cvarray memoryview")
def _(int L):
    cdef int i
    cdef double[::1] arr = cvarray((L,),sizeof(double),'d')

    cdef double d
    for i in range(loops):
        for i in range(L):
            d = arr[i]

    # Prevents dead-code elimination
    str(d)

出力:

INITIALISATIONS
Running cpython.array buffer
0.100040 0.097140 0.133110 0.121820 0.131630 0.108420 0.112160 μs
Running cpython.array memoryview
0.339480 0.333240 0.378790 0.445720 0.449800 0.414280 0.414060 μs
Running cpython.array raw C type
0.048270 0.049250 0.069770 0.074140 0.076300 0.060980 0.060270 μs
Running numpy.empty_like memoryview
1.006200 1.012160 1.128540 1.212350 1.250270 1.235710 1.241050 μs
Running malloc
0.021850 0.022430 0.037240 0.046260 0.039570 0.043690 0.030720 μs
Running malloc memoryview
1.640200 1.648000 1.681310 1.769610 1.755540 1.804950 1.758150 μs
Running cvarray memoryview
1.332330 1.353910 1.358160 1.481150 1.517690 1.485600 1.490790 μs

ITERATING
Running cpython.array buffer
0.010000 0.027000 0.091000 0.669000 6.314000 64.389000 635.171000 μs
Running cpython.array memoryview
0.013000 0.015000 0.058000 0.354000 3.186000 33.062000 338.300000 μs
Running cpython.array raw C type
0.014000 0.146000 0.979000 9.501000 94.160000 916.073000 9287.079000 μs
Running numpy.empty_like memoryview
0.042000 0.020000 0.057000 0.352000 3.193000 34.474000 333.089000 μs
Running malloc
0.002000 0.004000 0.064000 0.367000 3.599000 32.712000 323.858000 μs
Running malloc memoryview
0.019000 0.032000 0.070000 0.356000 3.194000 32.100000 327.929000 μs
Running cvarray memoryview
0.014000 0.026000 0.063000 0.351000 3.209000 32.013000 327.890000 μs

(「反復」ベンチマークの理由は、この点でいくつかのメソッドが驚くほど異なる特性を持っていることです。)

初期化速度の順:

malloc:これは厳しい世界ですが、高速です。多くのことを割り当てる必要があり、イテレーションとインデックス作成のパフォーマンスを妨げない場合、これが必要です。しかし、通常、あなたは良い賭けです...

cpython.array raw C type:まあ、それは速いです。そして、それは安全です。残念ながら、データフィールドにアクセスするためにPythonを通過します。これを回避するには、すばらしいトリックを使用します。

arr.data.as_doubles[i]

これにより、安全性が失われた状態で標準速度になります!これにより、これはmallocwonderful置き換えになり、基本的にはかなり参照カウントされたバージョンです!

cpython.array buffermallocのセットアップ時間の3倍から4倍で来ますが、これは素晴らしい賭けに見えます。残念ながら、オーバーヘッドは非常に大きくなります(boundscheckおよびwraparoundディレクティブと比較すると小さいですが)。つまり、完全に安全な亜種としか競合しませんが、は初期化が最も速いものです。あなたの選択。

cpython.array memoryview:これは、初期化するのにmallocよりも桁違いに遅くなりました。それは恥ずべきことですが、それは同じくらい速く繰り返されます。これは、boundscheckまたはwraparoundがオンになっていない限り、私が提案する標準的なソリューションです(この場合、cpython.array bufferは、より魅力的なトレードオフになる可能性があります)。

残り。オブジェクトに付加された多くの楽しいメソッドのために、何かに値する唯一のものはnumpyのものです。それだけです。

66
Veedrac

Veedracの答えのフォローアップとして:memoryviewcpython.arrayのサポートをpython 2.7で使用することに注意してください。現在、メモリリークが発生しているようです。 cython-usersメーリングリストで言及されている長年の問題 here 2012年11月の投稿。Cythonバージョン0.22でVeedracのベンチマークスクリプトをPython 2.7.6およびPython 2.7.9は、bufferまたはmemoryviewインターフェースのいずれかを使用してcpython.arrayを初期化するときに大きなメモリリークを引き起こします。 Python 3.4でスクリプトを実行しても、メモリリークは発生しません。これについてはCython開発者のメーリングリストにバグレポートを提出しました。

9
Matt Graham