2013-08-27 5 views
51

Die Cython documentation on typed memory views Liste drei Möglichkeiten, zu einer typisierten Speicher Ansicht zuweisen:Wie wird empfohlen, Speicher für eine typisierte Speicheransicht zuzuweisen?

  1. aus einem rohen C-Zeiger,
  2. von einem np.ndarray und
  3. von einem cython.view.array.

Nehmen wir an, ich habe keine Daten von außen zu meiner cython Funktion übergeben, sondern Speicher zuweisen möchten und senden Sie es als np.ndarray, welche dieser Optionen gewählt ich? Man nehme auch an, dass die Größe dieses Puffers ist keine Kompilierung-Konstante dh ich nicht auf dem Stapel zuordnen kann, würde aber zu malloc für Option benötigen 1.

Die drei Optionen daher so etwas wie dieses looke würde:

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 in allen drei Fällen, Cython generates quite a lot of code für die Speicherzuweisung, insbesondere ein Anruf an __Pyx_PyObject_to_MemoryviewSlice_dc_int. Dies legt nahe (und ich könnte hier falsch liegen, mein Einblick in die inneren Abläufe von Cython ist sehr begrenzt), dass es zuerst ein Python-Objekt erstellt und es dann in eine Speicheransicht "wirft", was unnötiger Overhead erscheint.

Eine simple benchmark offenbart keine großen Unterschiede zwischen den drei Methoden, wobei 2. mit dem geringsten Abstand am schnellsten ist.

Welche der drei Methoden wird empfohlen? Oder gibt es eine andere, bessere Option?

Follow-up Frage: Ich möchte schließlich das Ergebnis als np.ndarray zurückgeben, nachdem ich mit dieser Speicheransicht in der Funktion gearbeitet habe. Ist ein getippter Speicher die beste Wahl oder würde ich lieber die alte Pufferschnittstelle wie unten verwenden, um überhaupt eine ndarray zu erstellen?

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

Ausgezeichnete Frage, frage ich mich über etwas Ähnliches. – AlexE

+0

Ihr Maßstab ist die beste Antwort, die mir bekannt ist. Um die Folgefrage zu beantworten, können Sie einfach Ihr NumPy-Array auf die übliche Weise deklarieren (Sie müssen nicht einmal die alte Typ-Schnittstelle verwenden) und dann etwas wie "cdef int [:] arrview = arr" tun, um a zu erhalten Ansicht des gleichen Speichers, der für das NumPy-Array verwendet wird. Sie können die Ansicht für die schnelle Indizierung und das Übergeben von Segmenten 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. – IanH

+0

gibt es eine [gut verwandte Frage hier ...] (http://Stackoverflow.com/q/18410342/832621), wo Sie sehen können, dass np.empty langsam sein kann ... –

Antwort

56

Look here für eine Antwort.

Die Grundidee ist, dass Sie cpython.array.array und cpython.array.clone (nichtcython.array.*) wollen:

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

Es stellt sich heraus, dass die Benchmarks in diesem Thread waren Müll. Hier ist mein Satz, mit meinem 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) 

Ausgang:

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 das "Iterationen" Benchmark ist, dass einige Methoden haben überraschend unterschiedliche Eigenschaften in dieser Hinsicht)

In der Reihenfolge der Initialisierung Geschwindigkeit:

malloc: Dies ist eine harte Welt, aber es ist schnell.Wenn Sie eine Menge Dinge zuweisen müssen und eine ungehinderte Iterations- und Indizierungsleistung haben, muss das so sein. Aber normalerweise sind Sie eine gute Wette für ...

cpython.array raw C type: Nun verdammt, es ist schnell. Und es ist sicher. Leider geht es durch Python, um auf seine Datenfelder zuzugreifen. Sie können das vermeiden, indem Sie einen wunderbaren Trick verwenden:

was bringt es auf die Standardgeschwindigkeit, während die Sicherheit entfernt! Dies macht dies eine wunderbare Ersatz für malloc, im Grunde eine schöne Referenz-gezählte Version!

cpython.array buffer: In nur drei bis vier mal die Setup-Zeit von malloc kommen, das ist eine wunderbare Wette. Leider hat es einen erheblichen Overhead (wenn auch klein im Vergleich zu den boundscheck und wraparound Richtlinien). Das bedeutet, dass es nur wirklich gegen Vollsicherheitsvarianten konkurriert, aber es ist der schnellste von denen zu initialisieren. Deine Entscheidung.

cpython.array memoryview: Dies ist jetzt eine Größenordnung langsamer als malloc zu initialisieren. Das ist schade, aber es iteriert genauso schnell. Dies ist die Standardlösung, die ich vorschlagen würde, es sei denn, boundscheck oder wraparound sind eingeschaltet (in diesem Fall könnte cpython.array buffer eine zwingendere Abwägung sein).

Der Rest. Das einzig Wertvolle ist numpy, wegen der vielen lustigen Methoden, die an den Objekten angebracht sind. Das ist es aber.

+0

Danke für diese umfassende Umfrage und Unterstützung es mit Zahlen! – kynan

+1

Große Antwort! Habe ich recht damit, dass nur die reine "malloc" -Lösung die Notwendigkeit, die GIL zu erwerben, vollständig umgehen würde? Ich bin daran interessiert, mehrdimensionale Arrays in parallelen Worker-Threads zuzuordnen. –

+0

Probiere sie aus und melde dich zurück! – Veedrac

8

Als Follow-up zu Veedrac die Antwort: beachten Sie die memoryview Unterstützung von mit Python 2.7 scheint derzeit zu Speicherlecks führen. Dies scheint ein langjähriges Problem zu sein, wie es in einem Beitrag von November 2012 auf der Cython-Users-Mailingliste here erwähnt wird. Die Ausführung von Veedracs Benchmark-Scrip mit Cython Version 0.22 mit Python 2.7.6 und Python 2.7.9 führt zu a großer Speicherleck bei der Initialisierung eines cpython.array über eine buffer oder memoryview Schnittstelle. Beim Ausführen des Skripts mit Python 3.4 treten keine Speicherverluste auf. Ich habe einen Fehlerbericht in der Cython-Entwickler-Mailingliste eingereicht.