Zwingen Sie NumPy ndarray, den Besitz seines Speichers in Cython zu übernehmen
Lesezeit: 7 Minuten
Nach dieser Antwort auf “Kann ich einen numpy Ndarray zwingen, seinen Speicher in Besitz zu nehmen?” Ich habe versucht, die Python-C-API-Funktion zu verwenden PyArray_ENABLEFLAGS durch den NumPy-Wrapper von Cython und festgestellt, dass es nicht verfügbar ist.
Der folgende Versuch, es manuell verfügbar zu machen (dies ist nur ein minimales Beispiel, das den Fehler reproduziert)
from libc.stdlib cimport malloc
import numpy as np
cimport numpy as np
np.import_array()
ctypedef np.int32_t DTYPE_t
cdef extern from "numpy/ndarraytypes.h":
void PyArray_ENABLEFLAGS(np.PyArrayObject *arr, int flags)
def test():
cdef int N = 1000
cdef DTYPE_t *data = <DTYPE_t *>malloc(N * sizeof(DTYPE_t))
cdef np.ndarray[DTYPE_t, ndim=1] arr = np.PyArray_SimpleNewFromData(1, &N, np.NPY_INT32, data)
PyArray_ENABLEFLAGS(arr, np.NPY_ARRAY_OWNDATA)
Meine Frage: Ist das in diesem Fall der richtige Ansatz? Wenn ja, was mache ich falsch? Wenn nicht, wie zwinge ich NumPy, den Besitz von Cython zu übernehmen, ohne zu einem C-Erweiterungsmodul zu wechseln?
Hat meine Antwort für Sie funktioniert?
– Stefan
27. Mai ’14 um 7:11 Uhr
Das hat es tatsächlich getan, danke!
– Kynan
27. Mai ’14 um 22:13 Uhr
Stefan
Sie haben nur einige kleinere Fehler in der Schnittstellendefinition. Folgendes hat bei mir funktioniert:
from libc.stdlib cimport malloc
import numpy as np
cimport numpy as np
np.import_array()
ctypedef np.int32_t DTYPE_t
cdef extern from "numpy/arrayobject.h":
void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)
cdef data_to_numpy_array_with_spec(void * ptr, np.npy_intp N, int t):
cdef np.ndarray[DTYPE_t, ndim=1] arr = np.PyArray_SimpleNewFromData(1, &N, t, ptr)
PyArray_ENABLEFLAGS(arr, np.NPY_OWNDATA)
return arr
def test():
N = 1000
cdef DTYPE_t *data = <DTYPE_t *>malloc(N * sizeof(DTYPE_t))
arr = data_to_numpy_array_with_spec(data, N, np.NPY_INT32)
return arr
Das ist mein setup.py Datei:
from distutils.core import setup, Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("_owndata", ["owndata.pyx"])]
setup(cmdclass={'build_ext': build_ext}, ext_modules=ext_modules)
Bauen Sie mit python setup.py build_ext --inplace. Überprüfen Sie dann, ob die Daten tatsächlich Eigentum sind:
Und ja, das ist definitiv der richtige Weg, damit umzugehen, denn numpy.pxd macht genau dasselbe, um alle anderen Funktionen nach Cython zu exportieren.
Das funktioniert bei mir nicht. Es lässt sich gut kompilieren, aber das Importieren des Moduls führt zu einem Verknüpfungsfehler, der sich über PyArray_ENABLEFLAGS beschwert. Dies ist mit numpy 1.9.1.
– Amaurea
25. Oktober 15 um 17:36 Uhr
Diese Lösung funktioniert seit numpy 1.7. Älteren Versionen fehlt PyArray_ENABLEFLAGS
– Marscher
22. Januar 16 um 14:55 Uhr
Das scheitert mit ImportError: ./_owndata.so: undefined symbol: PyArray_ENABLEFLAGS
– Korem
20. Februar 16 um 19:55 Uhr
@Korem Hast du verwendet cdef extern from "numpy/arrayobject.h" ? Und vielleicht müssen Sie überprüfen, ob “numpy” in Ihrem Include-Pfad steht.
– Syrtis Major
22. Februar 16 um 1:56 Uhr
Was ist, wenn der Datentyp benutzerdefiniert ist? my_dtype = np.dtype([('t1', np.float32), ('t2', np.uint16)])? Mir ist klar, dass es eine hat type_num konnte aber nicht herausfinden, wie man es in Cython bekommt. Es ist definiert cdef np.ndarray[mytype_t] arr und mytype_t hat float und uint16 gepackt.
– schneidig
19. April 16 um 0:07 Uhr
lesen
@Stefans Lösung funktioniert für die meisten Szenarien, ist aber etwas zerbrechlich. Numpy verwendet PyDataMem_NEW/PyDataMem_FREE für die Speicherverwaltung und es ist ein Implementierungsdetail, dass diese Aufrufe auf das Übliche abgebildet werden malloc/free + etwas Speicherverfolgung (ich weiß nicht, welche Auswirkungen Stefans Lösung auf die Speicherverfolgung hat, zumindest scheint es nicht abzustürzen).
Es sind auch eher esoterische Fälle möglich, in denen free von numpy-library verwendet nicht denselben Speicherzuordner wie malloc im Cython-Code (verknüpft mit verschiedenen Laufzeiten zum Beispiel wie in this github-Problem oder diesen SO-Beitrag).
Das richtige Tool zum Übergeben/Verwalten des Eigentums an den Daten ist PyArray_SetBaseObject.
Zuerst brauchen wir ein Python-Objekt, das für die Freigabe des Speichers zuständig ist. Ich verwende hier eine selbst erstellte cdef-Klasse (hauptsächlich wegen Protokollierung/Demonstration), aber es gibt offensichtlich auch andere Möglichkeiten:
%%cython
from libc.stdlib cimport free
cdef class MemoryNanny:
cdef void* ptr # set to NULL by "constructor"
def __dealloc__(self):
print("freeing ptr=", <unsigned long long>(self.ptr)) #just for debugging
free(self.ptr)
@staticmethod
cdef create(void* ptr):
cdef MemoryNanny result = MemoryNanny()
result.ptr = ptr
print("nanny for ptr=", <unsigned long long>(result.ptr)) #just for debugging
return result
...
Jetzt verwenden wir a MemoryNanny-Objekt als Wächter für den Speicher, der freigegeben wird, sobald das Parent-Numpy-Array zerstört wird. Der Code ist etwas umständlich, weil PyArray_SetBaseObject stiehlt die Referenz, die von Cython nicht automatisch gehandhabt wird:
%%cython
...
from cpython.object cimport PyObject
from cpython.ref cimport Py_INCREF
cimport numpy as np
#needed to initialize PyArray_API in order to be able to use it
np.import_array()
cdef extern from "numpy/arrayobject.h":
# a little bit awkward: the reference to obj will be stolen
# using PyObject* to signal that Cython cannot handle it automatically
int PyArray_SetBaseObject(np.ndarray arr, PyObject *obj) except -1 # -1 means there was an error
cdef array_from_ptr(void * ptr, np.npy_intp N, int np_type):
cdef np.ndarray arr = np.PyArray_SimpleNewFromData(1, &N, np_type, ptr)
nanny = MemoryNanny.create(ptr)
Py_INCREF(nanny) # a reference will get stolen, so prepare nanny
PyArray_SetBaseObject(arr, <PyObject*>nanny)
return arr
...
Und hier ist ein Beispiel, wie diese Funktionalität aufgerufen werden kann:
>>> m = create()
nanny for ptr= 94339864945184
>>> m.flags
...
OWNDATA : False
...
>>> m[0]
42.0
>>> del m
freeing ptr= 94339864945184
mit Ergebnissen/Ausgabe wie erwartet.
Notiz: Die resultierenden Arrays besitzen die Daten nicht wirklich (dh Flags geben zurück OWNDATA : False), weil der Speicher der Speicher-Nanny gehört, aber das Ergebnis ist dasselbe: Der Speicher wird freigegeben, sobald das Array gelöscht wird (weil niemand mehr eine Referenz auf die Nanny hat).
MemoryNanny muss keinen rohen C-Zeiger bewachen. Es kann alles andere sein, zum Beispiel auch a std::vector:
%%cython -+
from libcpp.vector cimport vector
cdef class VectorNanny:
#automatically default initialized/destructed by Cython:
cdef vector[double] vec
@staticmethod
cdef create(vector[double]& vec):
cdef VectorNanny result = VectorNanny()
result.vec.swap(vec) # swap and not copy
return result
# for testing:
def create_vector(int N):
cdef vector[double] vec;
vec.resize(N, 2.0)
return VectorNanny.create(vec)
Dass die Nanny funktioniert, zeigt folgender Test:
nanny=create_vector(10**8) # top shows additional 800MB memory are used
del nanny # top shows, this additional memory is no longer used.
Mike Luis
Die neueste Cython-Version ermöglicht es Ihnen, mit minimaler Syntax auszukommen, wenn auch etwas mehr Overhead als die vorgeschlagenen Lösungen auf niedrigerer Ebene.
Dies allein führt nicht zu Eigentumsübertragungen.
Insbesondere wird mit diesem Aufruf via ein Cython-Array generiert array_cwrapper.
Dadurch entsteht ein cython.array, ohne Speicher zuzuweisen. Der cython.array nutzt die stdlib.hmalloc und free Standardmäßig wird also erwartet, dass Sie anstelle spezieller CPython/Numpy-Zuweisungen auch den Standard-Malloc verwenden.
free wird nur aufgerufen, wenn dafür Ownership gesetzt ist cython.array, was es standardmäßig nur ist, wenn es Daten zuweist. Für unseren Fall können wir es manuell einstellen über:
my_cyarr.free_data = True
Um also ein 1D-Array zurückzugeben, wäre es so einfach wie:
from cython.view cimport array as cvarray
# ...
cdef cvarray cvarr = <np.int32_t[:N]> data
cvarr.free_data = True
return np.asarray(cvarr)
.
5767500cookie-checkZwingen Sie NumPy ndarray, den Besitz seines Speichers in Cython zu übernehmenyes
Hat meine Antwort für Sie funktioniert?
– Stefan
27. Mai ’14 um 7:11 Uhr
Das hat es tatsächlich getan, danke!
– Kynan
27. Mai ’14 um 22:13 Uhr