programing

numpy/ctype을 사용하여 C-할당 메모리 버퍼를 노출하는 보다 안전한 방법은?

lovejava 2023. 10. 26. 20:27

numpy/ctype을 사용하여 C-할당 메모리 버퍼를 노출하는 보다 안전한 방법은?

공유 메모리 버퍼를 사용하여 내부 상태를 저장하는 C 라이브러리의 파이썬 바인딩을 작성합니다.이러한 버퍼의 할당 및 해제는 라이브러리 자체에서 Python 외부에서 수행되지만, Python 내에서 wraped constructor/destructor 함수를 호출하여 간접적으로 제어할 수 있습니다.일부 버퍼를 Python에 노출시켜서 데이터를 읽을 수 있도록 하고, 경우에 따라서는 값을 Python에 푸시하고 싶습니다.성능과 메모리 사용은 중요한 문제이므로 가능한 한 데이터 복사를 피하고 싶습니다.

현재 접근 방식은 actype 포인터에 직접 보기를 제공하는 numpy 배열을 만드는 것입니다.

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)

복사를 방지할 뿐만 아니라 numpy의 인덱싱 및 할당 구문을 사용하여 다른 numpy 함수에 직접 전달할 수 있습니다.

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]

하지만 이는 본질적으로 위험하기도 합니다.

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

이것을 더 안전하게 하기 위해 배열 내용을 읽기/쓰기하기 전에 기본 C 포인터가 해제되었는지 확인할 수 있어야 합니다.이 작업을 수행하는 방법에 대해 몇 가지 생각이 있습니다.

  • 한 가지 방법은 다음의 하위 클래스를 생성하는 것입니다.np.ndarray에 대한 언급이 있습니다._cbuf의 속성MyWrapper, 여부를 확인합니다.None기본 메모리에 읽기/쓰기를 수행하기 전에, 이 경우 예외를 제기합니다.
  • 예를 들어, 동일한 버퍼에 여러 뷰를 쉽게 생성할 수 있었습니다..view주조 또는 슬라이싱(slicing)을 수행하므로 이들 각각은 다음에 대한 참조를 상속해야 합니다._cbuf체크를 수행하는 방법.제가 보기엔 이 문제를 해결하기 위해서는__array_finalize__, 정확한 방법은 모르겠어요
  • 또한 배열의 내용을 읽고 쓰는 작업을 수행하기 전에 "포인트 검사" 방법을 호출해야 합니다.저는 눔피의 내부에 대해 충분히 알지 못해 무시할 수 있는 모든 방법들을 가지고 있습니다.

다음의 하위 클래스를 구현하려면 어떻게 해야 합니까?np.ndarray이 체크를 수행할 수 있습니까?더 나은 접근법을 제안해 줄 수 있는 사람?


업데이트: 이 클래스는 내가 원하는 대부분을 수행합니다.

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)

예를 들어,

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!

제가 놓친 또 다른 사건들이 있을 겁니다.


업데이트 2: 나는 장난을 친 적이 있습니다.weakref.proxy, @ivan_pozdeev가 제안한 것처럼.좋은 생각이지만 안타깝게도 Numpy 어레이에서 어떻게 작동할지 알 수 없습니다.나는 numpy 배열에 대한 weakref를 만들 수 있었습니다..buffer:

wrap = MyWrapper()
wr = weakref.proxy(wrap.buffer)
print(wr)
# ReferenceError: weakly-referenced object no longer exists
# <weakproxy at 0x7f6fe715efc8 to NoneType at 0x91a870>

여기서 문제가 되는 것은np.ndarray에 의해 반환된 예wrap.buffer즉시 범위를 벗어납니다.이를 해결할 수 있는 방법은 초기화 시 어레이를 인스턴스화하고 이에 대한 강력한 참조를 유지한 다음.buffer()답례품을 받다weakref.proxy배열:

class MyWrapper2(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)
        self._buffer = np.ctypeslib.as_array(self._cbuf)

    def __del__(self):
        # buffer freed by external library
        libc.free(C.addressof(self._cbuf))
        self._cbuf = None
        self._buffer = None

    @property
    def buffer(self):
        return weakref.proxy(self._buffer)

그러나 버퍼가 할당된 상태에서 동일한 배열에 두 번째 보기를 만들면 다음과 같이 중단됩니다.

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]  

전화 후에 심각하게 고장이 났습니다.wrap2.__del__()읽고 쓸 수 있을 뿐만 아니라buf2어렴풋한 배열로 보기엔wrap2._cbuf, 하지만 나는 심지어 읽고 쓸 수 있습니다.buf, 그것은 가능하지 않을 것입니다.wrap2.__del__()놓다wrap2._buffer.None.

Numpy 배열이 존재하는 동안 래퍼에 대한 참조를 유지해야 합니다.이를 달성하는 가장 쉬운 방법은 이 참조를 ctype-buffer의 속성에 저장하는 것입니다.

class MyWrapper(object):
    def __init__(self, n=10):
        # buffer allocated by external library
        self.size = n
        self.addr = libc.malloc(C.sizeof(C.c_int) * n)

    def __del__(self):
        # buffer freed by external library
        libc.free(self.addr)

    @property
    def buffer(self):
        buf = (C.c_int * self.size).from_address(self.addr)
        buf._wrapper = self
        return np.ctypeslib.as_array(buf)

이렇게 하면 마지막 참조(예: 마지막 numpy 배열)가 가비지를 수집하면 포장지가 자동으로 해제됩니다.

제3자가 작성하고 바이너리로 배포하는 독점 라이브러리입니다.파이썬이 아닌 C에서 같은 라이브러리 함수를 호출할 수는 있지만 버퍼를 실제로 할당하고 자유롭게 하는 코드에 대한 접근 권한이 아직 없기 때문에 별 도움이 되지 않을 것입니다.예를 들어 버퍼를 직접 할당한 다음 포인터로 라이브러리에 전달할 수 없습니다.

그러나 버퍼를 Python 확장자 유형으로 랩핑할 수도 있습니다.이렇게 하면 사용 가능한 인터페이스만 노출할 수 있고, 확장자 유형이 버퍼 해제를 자동으로 처리하도록 할 수 있습니다.그러면 파이썬 API에서 메모리 읽기/쓰기를 무료로 수행할 수 없습니다.


나의 버퍼

#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()

$ 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

@Vikas의 접근법이 마음에 들었는데 시도해보니 싱글의 Numpy object-array만 나옵니다.FreeOnDel물건. 다음은 효과가 있습니다.다음은 훨씬 단순하고 효과적입니다.

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)

어디에ptr는 데이터를 정수(예: )로 가리키는 포인터입니다.ctypesptr.addressof(...)).

이 속성은 Numpy에게 메모리 영역을 배열로 캐스트하는 방법을 알려주기에 충분합니다. 그리고 나서FreeOnDel개체가 해당 배열의 개체가 됩니다.배열이 삭제되면 삭제가 에 전파됩니다.FreeOnDelobject, 호출할 수 있는 곳libc.free.

이걸 부를 수도 있습니다.FreeOnDel클래스"BufferOwner", 그것이 바로 소유권을 추적하는 역할이기 때문입니다.

weakref는 제안하는 기능에 대해 내장된 메커니즘입니다.구체적으로, 는 참조된 인터페이스와 동일한 인터페이스를 갖는 객체입니다.참조된 개체를 폐기한 후 프록시에 대한 모든 작업이 발생합니다.weakref.ReferenceError. 당신은 필요도 없습니다.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

보시다시피, 어떤 경우든 C 버퍼를 통해 일반 Python 객체가 필요합니다.외부 라이브러리가 메모리를 소유하고 있는 경우 C 레벨에서 버퍼를 해제하기 전에 개체를 삭제해야 합니다.만약 당신이 스스로 메모리를 소유하고 있다면, 당신은 그냥 당신이 만든 것입니다.ctypes정상적인 방법으로 객체를 만들고, 그러면 삭제되면 자유로워집니다.

따라서 외부 라이브러리가 메모리를 소유하고 있고 언제든지 사용할 수 있는 경우(사용자의 사양이 이에 대해 모호함), 어떻게든 사용자에게 해당 메모리가 곧 사용될 것임을 알려주어야 합니다. 그렇지 않으면 필요한 작업을 수행하기 위해 해당 메모리에 대해 알 수 없습니다.

그냥 포장지에 추가가 있으면 됩니다.__del__에 전달하기 전에 기능합니다.numpy.ctypeslib.as_array방법.

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)

테스트하기

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

사실 더 쉬운 솔루션은 덮어쓰기입니다.__del__기능.

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

Python에서 C 버퍼의 수명을 완전히 제어할 수 있다면 기본적으로 가지고 있는 것은 Python "buffer" 개체입니다.ndarray사용해야 합니다.

따라서,

  • 이들을 연결하는 데에는 두 가지 기본적인 방법이 있습니다.
    • 버퍼 -> ndarray
    • ndarray -> 버퍼
  • 버퍼 자체를 어떻게 구현할 것인지에 대해서도 의문이 있습니다.

버퍼 -> ndarray

안전하지 않음: 자동적으로 참조를 유지하는 것이 없습니다.buffer평생 동안ndarray. 둘 다를 참조하기 위해 세 번째 개체를 도입하는 것은 더 나은 것이 아닙니다. 그러면 세 번째 개체를 추적하는 대신 계속해서 추적해야 합니다.buffer.

ndarray -> 버퍼

"이제 말을 하는군요!"바로 눈앞에 있는 일이 "완충을 하는 것"이기 때문에ndarray사용해야 합니까?이것이 자연스러운 길입니다.

.numpy메커니즘이 내장되어 있습니다: anyndarray자신의 기억을 가지고 있지 않은 것은 자신의 기억 속에 있는 물체에 대한 언급을 가지고 있습니다.base속성(후자가 쓰레기를 수집하는 것을 방지하는 thus).보기의 경우, 속성은 자동으로 그에 따라 할당됩니다(만약 다음과 같은 경우에는 상위 개체에 할당됨).base이다.None또는 부모님께base).

문제점은 오래된 물건을 그냥 둘 수 없다는 것입니다.대신, 속성은 생성자에 의해 채워지고 제안된 개체는 먼저 스크리너를 통과합니다.

그래서, 만약 우리가 어떤 맞춤형 물체를 만들 수 있다면,numpy.array메모리 재사용 가능 여부를 승인하고 고려합니다.numpy.ctypeslib.as_array실제로는 의 포장지입니다.numpy.array(copy=False)몇 번의 제정신 검사와 함께)...

<...>

언급URL : https://stackoverflow.com/questions/37988849/safer-way-to-expose-a-c-allocated-memory-buffer-using-numpy-ctypes