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)

schlägt mit einem Kompilierfehler fehl:

Error compiling Cython file:
------------------------------------------------------------
...
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)
                          ^
------------------------------------------------------------

/tmp/test.pyx:19:27: Cannot convert Python object to 'PyArrayObject *'

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

Zwingen Sie NumPy ndarray den Besitz seines Speichers in Cython
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:

import _owndata
arr = _owndata.test()
print arr.flags

Unter anderem sollten Sie sehen OWNDATA : True.

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


1642773966 90 Zwingen Sie NumPy ndarray den Besitz seines Speichers in Cython
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:

%%cython
...
from libc.stdlib cimport malloc
def create():
    cdef double *ptr=<double*>malloc(sizeof(double)*8);
    ptr[0]=42.0
    return array_from_ptr(ptr, 8, np.NPY_FLOAT64)

die wie folgt verwendet werden können:

>>> 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.

Zwingen Sie NumPy ndarray den Besitz seines Speichers in Cython
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.

numpy_array = np.asarray(<np.int32_t[:10, :10]> my_pointer)

https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#coercion-to-numpy

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.h malloc 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)

.

576750cookie-checkZwingen Sie NumPy ndarray, den Besitz seines Speichers in Cython zu übernehmen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy