In der Cython-Dokumentation zu typisierten Speicheransichten sind drei Möglichkeiten zum Zuweisen zu einer typisierten Speicheransicht aufgeführt:
- von einem rohen C-Zeiger,
- von einem
np.ndarray
und - von a
cython.view.array
.
Angenommen, ich habe keine Daten von außen an meine Cython-Funktion übergeben, sondern möchte stattdessen Speicher zuweisen und als zurückgeben. Welche np.ndarray
dieser Optionen habe ich ausgewählt? Nehmen Sie außerdem an, dass die Größe dieses Puffers keine Kompilierungszeitkonstante ist, dh ich kann sie nicht auf dem Stapel zuordnen, sondern malloc
für Option 1.
Die 3 Optionen würden daher ungefähr so aussehen:
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")
Was mich überrascht ist, dass Cython in allen drei Fällen ziemlich viel Code für die Speicherzuweisung generiert , insbesondere für einen Aufruf von __Pyx_PyObject_to_MemoryviewSlice_dc_int
. Dies deutet darauf hin (und ich könnte mich hier irren, meine Einsicht in das Innenleben von Cython ist sehr begrenzt), dass zuerst ein Python-Objekt erstellt und dann in eine Speicheransicht "umgewandelt" wird, was als unnötiger Aufwand erscheint.
Ein einfacher Benchmark zeigt keinen großen Unterschied zwischen den drei Methoden, wobei 2. mit geringem Abstand der schnellste ist.
Welche der drei Methoden wird empfohlen? Oder gibt es eine andere, bessere Option?
Folgefrage: Ich möchte das Ergebnis endlich als zurückgeben np.ndarray
, nachdem ich mit dieser Speicheransicht in der Funktion gearbeitet habe. Ist eine typisierte Speicheransicht die beste Wahl, oder würde ich lieber einfach die alte Pufferschnittstelle wie unten verwenden, um eine zu erstellen ndarray
?
cdef np.ndarray[DTYPE_t, ndim=1] b = np.empty(N, dtype=np.int32)
cdef int[:] arrview = arr
eine Ansicht des gleichen Speichers erhalten, der für das NumPy-Array verwendet wird . Sie können die Ansicht für die schnelle Indizierung und zum Übergeben von Slices zwischen Cython-Funktionen verwenden, während Sie weiterhin über das NumPy-Array auf die NumPy-Funktionen zugreifen können. Wenn Sie fertig sind, können Sie einfach das NumPy-Array zurückgeben.Antworten:
Suchen Sie hier nach einer Antwort.
Die Grundidee ist, dass Sie wollen
cpython.array.array
undcpython.array.clone
( nichtcython.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)
BEARBEITEN
Es stellt sich heraus, dass die Benchmarks in diesem Thread Müll waren. Hier ist mein Set mit meinen Timings:
# 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)
Ausgabe:
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
(Der Grund für den Benchmark "Iterationen" ist, dass einige Methoden in dieser Hinsicht überraschend unterschiedliche Eigenschaften aufweisen.)
In der Reihenfolge der Initialisierungsgeschwindigkeit:
malloc
: Dies ist eine raue Welt, aber es ist schnell. Wenn Sie viele Dinge zuordnen müssen und eine ungehinderte Iterations- und Indizierungsleistung haben müssen, muss dies der Fall sein. Aber normalerweise bist du eine gute Wette für ...cpython.array raw C type
: Verdammt, es ist schnell. Und es ist sicher. Leider geht es über Python, um auf seine Datenfelder zuzugreifen. Sie können dies vermeiden, indem Sie einen wunderbaren Trick anwenden:das bringt es auf die Standardgeschwindigkeit und entfernt gleichzeitig die Sicherheit! Dies macht dies zu einem wunderbaren Ersatz für
malloc
eine ziemlich referenzgezählte Version!cpython.array buffer
: Mit nur drei- bis viermaliger Einrichtungszeitmalloc
sieht dies eine wunderbare Wette aus. Leider hat es einen erheblichen Overhead (wenn auch im Vergleich zu den Richtlinienboundscheck
undwraparound
) gering ). Das heißt, es konkurriert nur wirklich mit Vollsicherheitsvarianten, aber es ist das am schnellsten zu initialisierende. Deine Entscheidung.cpython.array memoryview
: Dies ist jetzt eine Größenordnung langsamer alsmalloc
beim Initialisieren. Das ist eine Schande, aber es iteriert genauso schnell. Dies ist die Standardlösung, die ich vorschlagen würde, sofern sie nichtboundscheck
aktiviertwraparound
ist (in diesem Fallcpython.array buffer
könnte dies ein überzeugenderer Kompromiss sein).Der Rest. Das einzige, was etwas wert ist
numpy
, ist das, aufgrund der vielen lustigen Methoden, die mit den Objekten verbunden sind. Das war's aber schon.quelle
malloc
Lösung die Notwendigkeit, die GIL zu erwerben, vollständig umgehen würde? Ich interessiere mich für Möglichkeiten, mehrdimensionale Arrays in parallelen Arbeitsthreads zuzuweisen.Als Folge der Antwort von Veedrac: Beachten Sie, dass die
memoryview
Unterstützung voncpython.array
Python 2.7 derzeit zu Speicherverlusten führt. Dies scheint ein langjähriges Problem zu sein, da es auf der Mailingliste der Cython-Benutzer hier in einem Beitrag vom November 2012 erwähnt wird. Das Ausführen von Veedracs Benchmark-Scrip mit Cython Version 0.22 mit Python 2.7.6 und Python 2.7.9 führt zu a Ein großer Speicherverlust tritt auf, wenn eincpython.array
mit einerbuffer
oder einermemoryview
Schnittstelle initialisiert wird. Beim Ausführen des Skripts mit Python 3.4 treten keine Speicherverluste auf. Ich habe einen Fehlerbericht dazu an die Mailingliste der Cython-Entwickler gesendet.quelle