1. ホーム
  2. python

型付きメモリビューの推奨メモリ割り当て方法は?

2023-09-06 09:10:23

質問

その 型付きメモリビューに関するCythonのドキュメント には、型付けされたメモリビューに割り当てる3つの方法が記載されています。

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

cython関数に外部からデータを渡さずに、メモリを確保して np.ndarray として返したい場合、どのオプションを選べばよいのでしょうか?また、そのバッファのサイズはコンパイル時の定数ではないと仮定してください。 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 オブジェクトを作成して、それをメモリ ビューに "casts" することを示唆しています (ここで間違っているかもしれません、私の Cython の内部動作への洞察力は非常に限られています)。

A シンプルなベンチマーク は、3つの方式に大きな違いはなく、2.が僅差で最速でした。

3つの方法のうち、どれが推奨されるのでしょうか?あるいは、他に良い方法があるのでしょうか?

続いての質問です。 私は最終的に結果を np.ndarray として最終的に結果を返したい。型付けされたメモリビューが最適なのか、それとも以下のような古いバッファインターフェイスを使って ndarray を作成する方がいいのでしょうか?

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

どのように解決するのですか?

見てください。 ここで をご覧ください。

基本的な考え方は 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

(quot;iterations"ベンチマークの理由は、いくつかのメソッドはこの点で驚くほど異なる特性を持っているからです)。

初期化速度が速い順に

malloc : これは厳しい世界ですが、速いです。多くのものをアロケートし、妨げのない反復処理とインデックス作成のパフォーマンスが必要なら、これでなければならない。でも、ふつうは......。

cpython.array raw C type : いやあ、速いですねえ。そして安全だ。残念ながら、データフィールドにアクセスするためにPythonを経由しています。素晴らしいトリックを使えば、それを避けることができる。

arr.data.as_doubles[i]

というように、安全性を排除しつつ、標準的な速度に近づけています! これによって、これは 素晴らしい の代わりになります。 malloc は、基本的にかなり参照カウントされるバージョンです!

cpython.array buffer : の3倍から4倍のセットアップ時間です。 malloc のわずか3〜4倍のセットアップ時間で、これは素晴らしい賭けのように見えます。しかし残念ながら、これはかなりのオーバーヘッドがあります (ただし boundscheckwraparound ディレクティブ)。つまり、フルセーフティ・バリアントとしか競合しませんが であり、初期化が最も速いです。お好みでどうぞ。

cpython.array memoryview : と比べて一桁遅くなりました。 malloc よりも一桁遅くなりました。それは残念なことですが、同じように速く反復されます。これは、私が提案する標準的な解決策であり、もし boundscheck または wraparound がオンになっている場合(この場合 cpython.array buffer はより説得力のあるトレードオフかもしれません)。

残りは 唯一価値があるのは numpy のもので、オブジェクトに付属する多くの楽しいメソッドのためです。それだけなんですけどね。