Sicherer Weg, einen C-zugewiesenen Speicherpuffer mit numpy/ctypes verfügbar zu machen?
Lesezeit: 18 Minuten
ali_m
Ich schreibe Python-Bindungen für eine C-Bibliothek, die gemeinsam genutzte Speicherpuffer verwendet, um ihren internen Zustand zu speichern. Die Zuweisung und Freigabe dieser Puffer erfolgt außerhalb von Python durch die Bibliothek selbst, aber ich kann indirekt steuern, wann dies geschieht, indem ich eingeschlossene Konstruktor-/Destruktorfunktionen innerhalb von Python aufrufe. Ich möchte einige der Puffer für Python verfügbar machen, damit ich aus ihnen lesen und in einigen Fällen Werte an sie übertragen kann. Leistung und Speichernutzung sind wichtige Anliegen, daher möchte ich das Kopieren von Daten nach Möglichkeit vermeiden.
Mein aktueller Ansatz besteht darin, ein numpy-Array zu erstellen, das einen direkten Blick auf einen ctypes-Zeiger bietet:
import numpy as np
import ctypes as C
libc = C.CDLL('libc.so.6')
class MyWrapper(object):
def __init__(self, n=10):
# buffer allocated by external library
addr = libc.malloc(C.sizeof(C.c_int) * n)
self._cbuf = (C.c_int * n).from_address(addr)
def __del__(self):
# buffer freed by external library
libc.free(C.addressof(self._cbuf))
self._cbuf = None
@property
def buffer(self):
return np.ctypeslib.as_array(self._cbuf)
Neben der Vermeidung von Kopien bedeutet dies auch, dass ich die Indizierungs- und Zuweisungssyntax von numpy verwenden und direkt an andere numpy-Funktionen übergeben kann:
wrap = MyWrapper()
buf = wrap.buffer # buf is now a writeable view of a C-allocated buffer
buf[:] = np.arange(10) # this is pretty cool!
buf[::2] += 10
print(wrap.buffer)
# [10 1 12 3 14 5 16 7 18 9]
Es ist jedoch auch von Natur aus gefährlich:
del wrap # free the pointer
print(buf) # this is bad!
# [1852404336 1969367156 538978662 538976288 538976288 538976288
# 1752440867 1763734377 1633820787 8548]
# buf[0] = 99 # uncomment this line if you <3 segfaults
Um dies sicherer zu machen, muss ich überprüfen können, ob der zugrunde liegende C-Zeiger freigegeben wurde, bevor ich versuche, den Array-Inhalt zu lesen/schreiben. Ich habe ein paar Gedanken dazu, wie man das macht:
Eine Möglichkeit wäre, eine Unterklasse von zu generieren np.ndarray das enthält einen Verweis auf die _cbuf Attribut von MyWrapperüberprüft, ob dies der Fall ist None vor dem Lesen/Schreiben in den zugrunde liegenden Speicher und löst eine Ausnahme aus, wenn dies der Fall ist.
Ich könnte problemlos mehrere Ansichten auf denselben Puffer generieren, z. B. durch .view Casting oder Slicing, also müssten beide den Verweis auf erben _cbuf und die Methode, die die Prüfung durchführt. Ich vermute, dass dies durch Überschreiben erreicht werden könnte __array_finalize__aber ich weiß nicht genau wie.
Die “Pointer-Checking”-Methode müsste auch vor jeder Operation aufgerufen werden, die den Inhalt des Arrays lesen und/oder schreiben würde. Ich weiß nicht genug über die Interna von numpy, um eine vollständige Liste der zu überschreibenden Methoden zu haben.
Wie könnte ich eine Unterklasse von implementieren np.ndarray der diese Prüfung durchführt? Kann jemand einen besseren Ansatz vorschlagen?
Aktualisieren: Diese Klasse macht das meiste, was ich will:
class SafeBufferView(np.ndarray):
def __new__(cls, get_buffer, shape=None, dtype=None):
obj = np.ctypeslib.as_array(get_buffer(), shape).view(cls)
if dtype is not None:
obj.dtype = dtype
obj._get_buffer = get_buffer
return obj
def __array_finalize__(self, obj):
if obj is None: return
self._get_buffer = getattr(obj, "_get_buffer", None)
def __array_prepare__(self, out_arr, context=None):
if not self._get_buffer(): raise Exception("Dangling pointer!")
return out_arr
# this seems very heavy-handed - surely there must be a better way?
def __getattribute__(self, name):
if name not in ["__new__", "__array_finalize__", "__array_prepare__",
"__getattribute__", "_get_buffer"]:
if not self._get_buffer(): raise Exception("Dangling pointer!")
return super(np.ndarray, self).__getattribute__(name)
Zum Beispiel:
wrap = MyWrapper()
sb = SafeBufferView(lambda: wrap._cbuf)
sb[:] = np.arange(10)
print(repr(sb))
# SafeBufferView([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
print(repr(sb[::2]))
# SafeBufferView([0, 2, 4, 6, 8], dtype=int32)
sbv = sb.view(np.double)
print(repr(sbv))
# SafeBufferView([ 2.12199579e-314, 6.36598737e-314, 1.06099790e-313,
# 1.48539705e-313, 1.90979621e-313])
# we have to call the destructor method of `wrap` explicitly - `del wrap` won't
# do anything because `sb` and `sbv` both hold references to `wrap`
wrap.__del__()
print(sb) # Exception: Dangling pointer!
print(sb + 1) # Exception: Dangling pointer!
print(sbv) # Exception: Dangling pointer!
print(np.sum(sb)) # Exception: Dangling pointer!
print(sb.dot(sb)) # Exception: Dangling pointer!
print(np.dot(sb, sb)) # oops...
# -70104698
print(np.extract(np.ones(10), sb))
# array([251019024, 32522, 498870232, 32522, 4, 5,
# 6, 7, 48, 0], dtype=int32)
# np.copyto(sb, np.ones(10, np.int32)) # don't try this at home, kids!
Ich bin mir sicher, dass es noch andere Grenzfälle gibt, die ich übersehen habe.
Aktualisierung 2: Ich habe mit herumgespielt weakref.proxywie vorgeschlagen von @ivan_pozdeev. Es ist eine nette Idee, aber leider kann ich nicht sehen, wie es mit numpy Arrays funktionieren würde. Ich könnte versuchen, eine Weakref für das von zurückgegebene numpy-Array zu erstellen .buffer:
wrap = MyWrapper()
wr = weakref.proxy(wrap.buffer)
print(wr)
# ReferenceError: weakly-referenced object no longer exists
# <weakproxy at 0x7f6fe715efc8 to NoneType at 0x91a870>
Ich denke, das Problem hier ist, dass die np.ndarray Instanz zurückgegeben von wrap.buffer geht sofort aus dem Rahmen. Eine Problemumgehung wäre, dass die Klasse das Array bei der Initialisierung instanziiert, einen starken Verweis darauf hält und die .buffer() Getter-Rückkehr a weakref.proxy zum Array:
Dies wird jedoch unterbrochen, wenn ich eine zweite Ansicht auf dasselbe Array erstelle, während der Puffer noch zugewiesen ist:
wrap2 = MyWrapper2()
buf = wrap2.buffer
buf[:] = np.arange(10)
buf2 = buf[:] # create a second view onto the contents of buf
print(repr(buf))
# <weakproxy at 0x7fec3e709b50 to numpy.ndarray at 0x210ac80>
print(repr(buf2))
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
wrap2.__del__()
print(buf2[:]) # this is bad
# [1291716568 32748 1291716568 32748 0 0 0
# 0 48 0]
print(buf[:]) # WTF?!
# [34525664 0 0 0 0 0 0 0
# 0 0]
Das ist Ernsthaft kaputt – nach dem Anruf wrap2.__del__() Ich kann nicht nur lesen und schreiben buf2 das war eine numpy-Array-Ansicht auf wrap2._cbufaber ich kann sogar lesen und schreiben bufwas damit nicht möglich sein sollte wrap2.__del__() setzt wrap2._buffer zu None.
Haben Sie versucht, einen Wrapper mit Cython zu schreiben? Es hat eine sauberere (und wahrscheinlich sicherere) Schnittstelle zum Abrufen von Ansichten von Speicherpuffern über “typisierte Speicheransichten”.
– JoshAdel
23. Juni 2016 um 11:16 Uhr
@JoshAdel Würden getippte Speicheransichten in diesem Fall wirklich helfen? Letztendlich besteht das Problem darin, dass die Zuweisung und Freigabe dieser Puffer außerhalb von Python durch eine externe Bibliothek erfolgt, über die ich keine direkte Kontrolle habe. Das Beste, was ich tun kann, ist zu verfolgen, ob sie sollen noch zuzuweisen, basierend auf den Bibliotheksfunktionen, die ich bisher aufgerufen habe. Ich nehme an, ich könnte meine Buchhaltung in Cython statt in Python führen, aber ich sehe noch keinen überzeugenden Vorteil darin, zu Cython zu wechseln (es gibt einige andere Gründe, warum dies zu diesem Zeitpunkt schmerzhaft wäre).
– ali_m
23. Juni 2016 um 11:35 Uhr
Wenn Sie einen starken Verweis auf ein Objekt beibehalten, das den Deleter für Sie aufruft (z cffi (was du solltest stets verwenden statt ctypes) hat eingebaute Unterstützung für einen Deleter mit der gc -Methode), müssen Sie sich keine Gedanken über entwertete schwache Referenzen machen.
– o11c
24. Juni 2016 um 3:24 Uhr
@o11c gc ist hier irrelevant, da die Puffer außerhalb von Python zugewiesen und freigegeben werden, indem die Bibliothek, die ich umschließe, extern ist.
– ali_m
24. Juni 2016 um 7:43 Uhr
@ali_m: Zuweisen _buffer = None befreit nicht _buffer, da das andere Array immer noch einen Verweis darauf hat. Wenn Sie manuell eine Funktion aufrufen, die Ihren Zeiger freigibt, bevor Ihr Zeiger freigegeben werden kann, wird das Zeug kaputt gehen.
– Benutzer2357112
28. Juni 2016 um 21:26 Uhr
Sie müssen einen Verweis auf Ihren Wrapper behalten, während irgendein numpy-Array existiert. Der einfachste Weg, dies zu erreichen, besteht darin, diese Referenz in einem Attribut des ctype-buffers zu speichern:
Auf diese Weise wird Ihr Wrapper automatisch freigegeben, wenn die letzte Referenz, zB das letzte numpy-Array, der Garbage Collection unterzogen wird.
Es ist eine proprietäre Bibliothek, die von einem Drittanbieter geschrieben und als Binärdatei vertrieben wird. Ich könnte dieselben Bibliotheksfunktionen von C statt von Python aufrufen, aber das würde nicht viel helfen, da ich immer noch keinen Zugriff auf den Code habe, der die Puffer tatsächlich zuweist und freigibt. Ich kann zum Beispiel die Puffer nicht selbst zuweisen und sie dann als Zeiger an die Bibliothek übergeben.
Sie könnten den Puffer jedoch in einen Python-Erweiterungstyp einschließen. Auf diese Weise können Sie nur die Schnittstelle verfügbar machen, die verfügbar sein soll, und den Erweiterungstyp automatisch die Freigabe des Puffers vornehmen lassen. Auf diese Weise ist es der Python-API nicht möglich, im freien Speicher zu lesen/schreiben.
mybuffer.c
#include <python3.3/Python.h>
// Hardcoded values
// N.B. Most of these are only needed for defining the view in the Python
// buffer protocol
static long external_buffer_size = 32; // Size of buffer in bytes
static long external_buffer_shape[] = { 32 }; // Number of items for each dimension
static long external_buffer_strides[] = { 1 }; // Size of item for each dimension
//----------------------------------------------------------------------------
// Code to simulate the third-party library
//----------------------------------------------------------------------------
// Allocate a new buffer
static void* external_buffer_allocate()
{
// Allocate the memory
void* ptr = malloc(external_buffer_size);
// Debug
printf("external_buffer_allocate() = 0x%lx\n", (long) ptr);
// Fill buffer with a recognizable pattern
int i;
for (i = 0; i < external_buffer_size; ++i)
{
*((char*) ptr + i) = i;
}
// Done
return ptr;
}
// Free an existing buffer
static void external_buffer_free(void* ptr)
{
// Debug
printf("external_buffer_free(0x%lx)\n", (long) ptr);
// Release the memory
free(ptr);
}
//----------------------------------------------------------------------------
// Define a new Python instance object for the external buffer
// See: https://docs.python.org/3/extending/newtypes.html
//----------------------------------------------------------------------------
typedef struct
{
// Python macro to include standard members, like reference count
PyObject_HEAD
// Base address of allocated memory
void* ptr;
} BufferObject;
//----------------------------------------------------------------------------
// Define the instance methods for the new object
//----------------------------------------------------------------------------
// Called when there are no more references to the object
static void BufferObject_dealloc(BufferObject* self)
{
external_buffer_free(self->ptr);
}
// Called when we want a new view of the buffer, using the buffer protocol
// See: https://docs.python.org/3/c-api/buffer.html
static int BufferObject_getbuffer(BufferObject *self, Py_buffer *view, int flags)
{
// Set the view info
view->obj = (PyObject*) self;
view->buf = self->ptr; // Base pointer
view->len = external_buffer_size; // Length
view->readonly = 0;
view->itemsize = 1;
view->format = "B"; // unsigned byte
view->ndim = 1;
view->shape = external_buffer_shape;
view->strides = external_buffer_strides;
view->suboffsets = NULL;
view->internal = NULL;
// We need to increase the reference count of our buffer object here, but
// Python will automatically decrease it when the view goes out of scope
Py_INCREF(self);
// Done
return 0;
}
//----------------------------------------------------------------------------
// Define the struct required to implement the buffer protocol
//----------------------------------------------------------------------------
static PyBufferProcs BufferObject_as_buffer =
{
// Create new view
(getbufferproc) BufferObject_getbuffer,
// Release an existing view
(releasebufferproc) 0,
};
//----------------------------------------------------------------------------
// Define a new Python type object for the external buffer
//----------------------------------------------------------------------------
static PyTypeObject BufferType =
{
PyVarObject_HEAD_INIT(NULL, 0)
"external buffer", /* tp_name */
sizeof(BufferObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor) BufferObject_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
&BufferObject_as_buffer, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"External buffer", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc) 0, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};
//----------------------------------------------------------------------------
// Define a Python function to put in the module which creates a new buffer
//----------------------------------------------------------------------------
static PyObject* mybuffer_create(PyObject *self, PyObject *args)
{
BufferObject* buf = (BufferObject*)(&BufferType)->tp_alloc(&BufferType, 0);
buf->ptr = external_buffer_allocate();
return (PyObject*) buf;
}
//----------------------------------------------------------------------------
// Define the set of all methods which will be exposed in the module
//----------------------------------------------------------------------------
static PyMethodDef mybufferMethods[] =
{
{"create", mybuffer_create, METH_VARARGS, "Create a buffer"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
//----------------------------------------------------------------------------
// Define the module
//----------------------------------------------------------------------------
static PyModuleDef mybuffermodule = {
PyModuleDef_HEAD_INIT,
"mybuffer",
"Example module that creates an extension type.",
-1,
mybufferMethods
//NULL, NULL, NULL, NULL, NULL
};
//----------------------------------------------------------------------------
// Define the module's entry point
//----------------------------------------------------------------------------
PyMODINIT_FUNC PyInit_mybuffer(void)
{
PyObject* m;
if (PyType_Ready(&BufferType) < 0)
return NULL;
m = PyModule_Create(&mybuffermodule);
if (m == NULL)
return NULL;
return m;
}
test.py
#!/usr/bin/env python3
import numpy as np
import mybuffer
def test():
print('Create buffer')
b = mybuffer.create()
print('Print buffer')
print(b)
print('Create memoryview')
m = memoryview(b)
print('Print memoryview shape')
print(m.shape)
print('Print memoryview format')
print(m.format)
print('Create numpy array')
a = np.asarray(b)
print('Print numpy array')
print(repr(a))
print('Change every other byte in numpy')
a[::2] += 10
print('Print numpy array')
print(repr(a))
print('Change first byte in memory view')
m[0] = 42
print('Print numpy array')
print(repr(a))
print('Delete buffer')
del b
print('Delete memoryview')
del m
print('Delete numpy array - this is the last ref, so should free memory')
del a
print('Memory should be free before this line')
if __name__ == '__main__':
test()
Beispiel
$ gcc -fPIC -shared -o mybuffer.so mybuffer.c -lpython3.3m
$ ./test.py
Create buffer
external_buffer_allocate() = 0x290fae0
Print buffer
<external buffer object at 0x7f7231a2cc60>
Create memoryview
Print memoryview shape
(32,)
Print memoryview format
B
Create numpy array
Print numpy array
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], dtype=uint8)
Change every other byte in numpy
Print numpy array
array([10, 1, 12, 3, 14, 5, 16, 7, 18, 9, 20, 11, 22, 13, 24, 15, 26,
17, 28, 19, 30, 21, 32, 23, 34, 25, 36, 27, 38, 29, 40, 31], dtype=uint8)
Change first byte in memory view
Print numpy array
array([42, 1, 12, 3, 14, 5, 16, 7, 18, 9, 20, 11, 22, 13, 24, 15, 26,
17, 28, 19, 30, 21, 32, 23, 34, 25, 36, 27, 38, 29, 40, 31], dtype=uint8)
Delete buffer
Delete memoryview
Delete numpy array - this is the last ref, so should free memory
external_buffer_free(0x290fae0)
Memory should be free before this line
Ich mochte den Ansatz von @Vikas, aber als ich es versuchte, bekam ich nur ein Numpy-Objekt-Array von einem einzigen FreeOnDel Objekt. Folgendes ist viel einfacher und funktioniert:
class FreeOnDel(object):
def __init__(self, data, shape, dtype, readonly=False):
self.__array_interface__ = {"version": 3,
"typestr": numpy.dtype(dtype).str,
"data": (data, readonly),
"shape": shape}
def __del__(self):
data = self.__array_interface__["data"][0] # integer ptr
print("do what you want with the data at {}".format(data))
view = numpy.array(FreeOnDel(ptr, shape, dtype), copy=False)
wo ptr ist ein Zeiger auf die Daten als ganze Zahl (zB ctypesptr.addressof(...)).
Dies __array_interface__ Das Attribut reicht aus, um Numpy mitzuteilen, wie ein Speicherbereich in ein Array umgewandelt werden soll, und dann das FreeOnDel Objekt wird dieses Array base. Wenn das Array gelöscht wird, wird die Löschung an die weitergegeben FreeOnDel Objekt, wo Sie anrufen können libc.free.
Ich könnte das sogar nennen FreeOnDel Klasse “BufferOwner“, denn das ist seine Aufgabe: die Eigentümerschaft nachzuverfolgen.
ivan_pozdeev
weakref ist ein integrierter Mechanismus für die von Ihnen vorgeschlagene Funktionalität. Speziell, weakref.proxy ist ein Objekt mit derselben Schnittstelle wie das referenzierte. Nach der Entsorgung des referenzierten Objekts wird jede Operation auf dem Proxy ausgelöst weakref.ReferenceError. Du brauchst nicht einmal numpy:
In [2]: buffer=(c.c_int*100)() #acts as an example for an externally allocated buffer
In [3]: voidp=c.addressof(buffer)
In [10]: a=(c.c_int*100).from_address(voidp) # python object accessing the buffer.
# Here it's created from raw address value. It's better to use function
# prototypes instead for some type safety.
In [14]: ra=weakref.proxy(a)
In [15]: a[1]=1
In [16]: ra[1]
Out[16]: 1
In [17]: del a
In [18]: ra[1]
ReferenceError: weakly-referenced object no longer exists
In [20]: buffer[1]
Out[20]: 1
Wie Sie sehen, benötigen Sie in jedem Fall ein normales Python-Objekt über dem C-Puffer. Besitzt eine externe Bibliothek den Speicher, muss das Objekt gelöscht werden, bevor der Puffer auf C-Ebene freigegeben wird. Wenn Sie den Speicher selbst besitzen, erstellen Sie einfach eine ctypes Objekt auf die übliche Weise, dann wird es freigegeben, wenn es gelöscht wird.
Wenn also Ihre externe Bibliothek den Speicher besitzt und jederzeit freigeben kann (Ihre Spezifikation ist diesbezüglich vage), muss sie Ihnen irgendwie mitteilen, dass dies im Begriff ist – andernfalls haben Sie keine Möglichkeit, dies zu erfahren, um die erforderlichen Maßnahmen zu ergreifen.
Vikas
Sie benötigen lediglich eine Hülle mit zusätzlichem __del__ Funktion, bevor Sie sie an die übergeben numpy.ctypeslib.as_array Methode.
class FreeOnDel(object):
def __init__(self, ctypes_ptr):
# This is not needed if you are dealing with ctypes.POINTER() objects
# Start of hack for ctypes ARRAY type;
if not hasattr(ctypes_ptr, 'contents'):
# For static ctypes arrays, the length and type are stored
# in the type() rather than object. numpy queries these
# properties to find out the shape and type, hence needs to be
# copied. I wish type() properties could be automated by
# __getattr__ too
type(self)._length_ = type(ctypes_ptr)._length_
type(self)._type_ = type(ctypes_ptr)._type_
# End of hack for ctypes ARRAY type;
# cannot call self._ctypes_ptr = ctypes_ptr because of recursion
super(FreeOnDel, self).__setattr__('_ctypes_ptr', ctypes_ptr)
# numpy.ctypeslib.as_array function sets the __array_interface__
# on type(ctypes_ptr) which is not called by __getattr__ wrapper
# Hence this additional wrapper.
@property
def __array_interface__(self):
return self._ctypes_ptr.__array_interface__
@__array_interface__.setter
def __array_interface__(self, value):
self._ctypes_ptr.__array_interface__ = value
# This is the onlly additional function we need rest all is overhead
def __del__(self):
addr = ctypes.addressof(self._ctypes_ptr)
print("freeing address %x" % addr)
libc.free(addr)
# Need to be called on all object members
# object.__del__(self) does not work
del self._ctypes_ptr
def __getattr__(self, attr):
return getattr(self._ctypes_ptr, attr)
def __setattr__(self, attr, val):
setattr(self._ctypes_ptr, attr, val)
Zu testen
In [32]: import ctypes as C
In [33]: n = 10
In [34]: libc = C.CDLL("libc.so.6")
In [35]: addr = libc.malloc(C.sizeof(C.c_int) * n)
In [36]: cbuf = (C.c_int * n).from_address(addr)
In [37]: wrap = FreeOnDel(cbuf)
In [38]: sb = np.ctypeslib.as_array(wrap, (10,))
In [39]: sb[:] = np.arange(10)
In [40]: print(repr(sb))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
In [41]: print(repr(sb[::2]))
array([0, 2, 4, 6, 8], dtype=int32)
In [42]: sbv = sb.view(np.double)
In [43]: print(repr(sbv))
array([ 2.12199579e-314, 6.36598737e-314, 1.06099790e-313,
1.48539705e-313, 1.90979621e-313])
In [45]: buf2 = sb[:8]
In [46]: sb[::2] += 10
In [47]: del cbuf # Memory not freed because this does not have __del__
In [48]: del wrap # Memory not freed because sb, sbv, buf2 have references
In [49]: del sb # Memory not freed because sbv, buf have references
In [50]: del buf2 # Memory not freed because sbv has reference
In [51]: del sbv # Memory freed because no more references
freeing address 2bc6bc0
Tatsächlich ist eine einfachere Lösung das Überschreiben __del__ Funktion
In [7]: olddel = getattr(cbuf, '__del__', lambda: 0)
In [8]: cbuf.__del__ = lambda self : libc.free(C.addressof(self)), olddel
In [10]: import numpy as np
In [12]: sb = np.ctypeslib.as_array(cbuf, (10,))
In [13]: sb[:] = np.arange(10)
In [14]: print(repr(sb))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
In [15]: print(repr(sb))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int32)
In [16]: print(repr(sb[::2]))
array([0, 2, 4, 6, 8], dtype=int32)
In [17]: sbv = sb.view(np.double)
In [18]: print(repr(sbv))
array([ 2.12199579e-314, 6.36598737e-314, 1.06099790e-313,
1.48539705e-313, 1.90979621e-313])
In [19]: buf2 = sb[:8]
In [20]: sb[::2] += 10
In [22]: del cbuf # Memory not freed
In [23]: del sb # Memory not freed because sbv, buf have references
In [24]: del buf2 # Memory not freed because sbv has reference
In [25]: del sbv # Memory freed because no more references
Wenn Sie die Lebensdauer des C-Puffers vollständig von Python aus steuern können, haben Sie im Wesentlichen ein Python-„Puffer“-Objekt, das an ndarray sollte nutzen.
Daher,
Es gibt zwei grundlegende Möglichkeiten, sie zu verbinden:
Puffer -> ndarray
ndarray -> Puffer
Es stellt sich auch die Frage, wie der Puffer selbst implementiert werden soll
Puffer -> ndarray
Ist unsicher: Es gibt nichts, was automatisch einen Verweis darauf enthält buffer für die Lebenszeit von ndarray. Es ist nicht besser, ein drittes Objekt einzuführen, um Verweise auf beide zu enthalten: Dann müssen Sie nur das dritte Objekt im Auge behalten, anstatt das buffer.
ndarray -> Puffer
“Jetzt redest du!” Da die eigentliche Aufgabe darin besteht, “Puffer, dass ein ndarray verwenden sollten”? Dies ist der natürliche Weg.
In der Tat, numpy hat einen eingebauten Mechanismus: beliebig ndarray das seinen Speicher nicht besitzt, enthält einen Verweis auf das Objekt, das ihn besitzt base -Attribut (wodurch verhindert wird, dass letzteres von der Garbage Collection erfasst wird). Bei Ansichten wird das Attribut automatisch entsprechend zugewiesen (dem übergeordneten Objekt, falls dessen base ist None oder zu den Eltern base).
Der Haken ist, dass Sie dort nicht einfach irgendein altes Objekt platzieren können. Stattdessen wird das Attribut von einem Konstruktor gefüllt und das vorgeschlagene Objekt zunächst auf die Probe gestellt.
Wenn wir also nur ein benutzerdefiniertes Objekt erstellen könnten numpy.array akzeptiert und hält die Wiederverwendung des Speichers für möglich (numpy.ctypeslib.as_array ist eigentlich ein Wrapper für numpy.array(copy=False) mit ein paar Gesundheitschecks) …
<...>
13702800cookie-checkSicherer Weg, einen C-zugewiesenen Speicherpuffer mit numpy/ctypes verfügbar zu machen?yes
Haben Sie versucht, einen Wrapper mit Cython zu schreiben? Es hat eine sauberere (und wahrscheinlich sicherere) Schnittstelle zum Abrufen von Ansichten von Speicherpuffern über “typisierte Speicheransichten”.
– JoshAdel
23. Juni 2016 um 11:16 Uhr
@JoshAdel Würden getippte Speicheransichten in diesem Fall wirklich helfen? Letztendlich besteht das Problem darin, dass die Zuweisung und Freigabe dieser Puffer außerhalb von Python durch eine externe Bibliothek erfolgt, über die ich keine direkte Kontrolle habe. Das Beste, was ich tun kann, ist zu verfolgen, ob sie sollen noch zuzuweisen, basierend auf den Bibliotheksfunktionen, die ich bisher aufgerufen habe. Ich nehme an, ich könnte meine Buchhaltung in Cython statt in Python führen, aber ich sehe noch keinen überzeugenden Vorteil darin, zu Cython zu wechseln (es gibt einige andere Gründe, warum dies zu diesem Zeitpunkt schmerzhaft wäre).
– ali_m
23. Juni 2016 um 11:35 Uhr
Wenn Sie einen starken Verweis auf ein Objekt beibehalten, das den Deleter für Sie aufruft (z
cffi
(was du solltest stets verwenden stattctypes
) hat eingebaute Unterstützung für einen Deleter mit dergc
-Methode), müssen Sie sich keine Gedanken über entwertete schwache Referenzen machen.– o11c
24. Juni 2016 um 3:24 Uhr
@o11c
gc
ist hier irrelevant, da die Puffer außerhalb von Python zugewiesen und freigegeben werden, indem die Bibliothek, die ich umschließe, extern ist.– ali_m
24. Juni 2016 um 7:43 Uhr
@ali_m: Zuweisen
_buffer = None
befreit nicht_buffer
, da das andere Array immer noch einen Verweis darauf hat. Wenn Sie manuell eine Funktion aufrufen, die Ihren Zeiger freigibt, bevor Ihr Zeiger freigegeben werden kann, wird das Zeug kaputt gehen.– Benutzer2357112
28. Juni 2016 um 21:26 Uhr