2013-08-27 3 views
51

Cython documentation on typed memory viewsリスト型付きのメモリビューに割り当て、次の3つの方法:cython.view.arrayから型付きメモリビューに推奨されるメモリの割り当て方法は何ですか?

生Cポインタから
  1. np.ndarray
  2. からと

は私が外から私のcython関数に渡されたデータを持っているが、代わりにメモリを割り当て、私が選んだんこれらのオプションのnp.ndarray、としてそれを返すようにしたくないと仮定しますか?また、そのバッファのサイズは、私がスタック上に割り当てることができない、コンパイル時定数すなわちではなく、3つのオプションは、したがって、このような何かをlookeう1.

オプションのmallocに必要となることを想定しています。

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 generates quite a lot of code、特に__Pyx_PyObject_to_MemoryviewSlice_dc_intを呼び出したことです。これは、Pythonオブジェクトを最初に作成し、それをメモリビューに「キャスト」することで、不要なオーバーヘッドに見えることを示唆しています(そして、私はここで間違っているかもしれませんが、Cythonの内部動作についての私の洞察は非常に限られています)。

simple benchmarkは、3つの方法の間に大きな違いがあることを明らかにしていません.2が最も薄いマージンで最も速いです。

推奨する3つの方法はどれですか?あるいは、より良い選択肢がありますか?

フォローアップの質問:私は最終的に結果をnp.ndarrayとして返したいと思います。型付きメモリの表示が最良の選択ですか、ちょうどちょうどndarrayを最初に作成するために以下のような古いバッファインタフェースを使用しますか?

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

優秀な質問、私は何か類似しているのだろうか。 – AlexE

+0

あなたのベンチマークは私が知っている最高の答えです。フォローアップの質問に答えるには、通常の方法でNumPy配列を宣言するだけです(古い型のインターフェースを使用する必要はありません)。次に 'cdef int [:] arrview = arr'のようなことをしてNumPy配列に使用された同じメモリのビュー。 NumPy配列を介してNumPy関数にアクセスしながら、高速インデックス作成とCython関数間でスライスを渡すためにビューを使用することができます。完了したら、NumPy配列を返すことができます。 – IanH

+0

ここには良い関連の質問があります...(http://stackoverflow.com/q/18410342/832621)ここでnp.emptyが遅くなることがわかります... –

答えて

56

答えはhereです。

基本的な考え方は、あなたがcpython.array.arraycpython.array.cloneないcython.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] 

これは安全を取り除く間にそれを標準速度に持ち上げます!これにより、の代わりにmallocに置き換わり、基本的にはかなり参考になりました!

cpython.array buffer:設定時間がmallocの3倍から4倍になると、これは素晴らしい選択です。残念ながら、それにはかなりのオーバーヘッドがあります(boundscheckwraparoundディレクティブと比較しても小さいですが)。つまり、実際に完全な安全性のバリアントと競合するだけですが、です。あなたの選択。

cpython.array memoryview:これは、現在はmallocよりもわずかに遅くなります。それは残念ですが、それは同じくらい速く繰り返されます。これは、boundscheckまたはwraparoundがオンになっていない限り、私が示唆している標準的な解決策です(この場合、cpython.array bufferがより魅力的なトレードオフになる可能性があります)。

残り。価値のある唯一のものはnumpyです。オブジェクトに付けられた楽しい方法がたくさんあるためです。それはそれです。

+0

その包括的なアンケートとバッキング数字でそれをアップ! – kynan

+1

すばらしい答え!純粋な 'malloc'ソリューションだけがGILを取得する必要性を完全に回避すると思うのは正しいですか?私は、並列作業スレッド内に多次元配列を割り当てる方法に興味があります。 –

+0

それらを試して、報告してください! – Veedrac

8

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

関連する問題