在boost :: python :: ndarray中设置所有者,以便数据由Python拥有和管理

时间:2019-07-17 04:15:45

标签: boost boost-python

我正在尝试使用boost :: python :: numpy :: ndarray在C ++中创建一个多维数组并将其传递给python。问题是如何做到这一点而不必自己管理C ++中与ndarray相关的内存。

我正在尝试使用boost :: python :: numpy :: from_data函数在C ++中创建numpy数组。我的基本理解是,在没有适当的函数所有者参数的情况下,管理与数组关联的内存的责任落在了我身上。

我最初的假设是,不必根据boost page来传递owner参数,它是关于owner的:“数据的所有者,以防它不是ndarray本身。”

但是,我读过一些似乎相反的帖子。例如,link说,“如果将object()作为所有者参数传递,则该数组肯定应该拥有其数据(因此报告OWNDATA = True)...”,link说该对象必须与显式析构函数相关联。

我想知道什么是正确的方法。还是这不是boost :: python :: numpy的预期用例?

1 个答案:

答案 0 :(得分:1)

是的,文档说如果您传递object(),则数组将在释放数据时释放数据,但这并不能像宣传的那样起作用。

以下是我在github https://github.com/boostorg/python/issues/97#issuecomment-519679003上的帖子的一些摘录,这是OP链接的相同问题,但以前没有答案。答案不是来自我,而是答案的演示以及为什么object()演示无法正常工作。

解决方案是创建一个拥有原始指针的对象(胶囊)并将其传递给boost::python::numpy::ndarray::from_data()函数。胶囊是管理指针的Python对象:

PyObject* PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor)

这是一个创建大型数组的示例。我将在Python while True:循环中重复调用此函数。使用此功能,您可以整天进行循环。这是因为在循环中,我将返回值分配给相同的变量,因此在每次迭代中,先前返回的ndarray的refcount变为零,并且内存被释放。

typedef long int my_data_type;
inline void destroyManagerCObject(PyObject* self) {
    auto * b = reinterpret_cast<my_data_type*>( PyCapsule_GetPointer(self, NULL) );
    std::cout << "C++      : " << __PRETTY_FUNCTION__ << " delete [] " << b << std::endl;
    delete [] b;
}

boost::python::numpy::ndarray get_array_that_owns_through_capsule()
{
    // Change this to see how the adresses change.
    unsigned int last_dim = 6000;
    boost::python::object shape = boost::python::make_tuple(4, 5, last_dim);

    boost::python::numpy::dtype dt = boost::python::numpy::dtype::get_builtin<my_data_type>();

    auto * const data_ptr = new my_data_type[4*5*last_dim];

    const size_t s = sizeof(my_data_type);
    boost::python::object strides = boost::python::make_tuple(5*last_dim*s, last_dim*s, s);

    for(int i = 1; i <= 4*5*last_dim; ++i){ data_ptr[i-1] = i; }

    // This sets up a python object whose destruction will free data_ptr
    PyObject *capsule = ::PyCapsule_New((void *)data_ptr, NULL, (PyCapsule_Destructor)&destroyManagerCObject);
    boost::python::handle<> h_capsule{capsule};
    boost::python::object owner_capsule{h_capsule};

    std::cout << "C++      : " << __PRETTY_FUNCTION__ << "data_ptr = " << data_ptr << std::endl;

    return boost::python::numpy::from_data( data_ptr, dt, shape, strides, owner_capsule);
}
BOOST_PYTHON_MODULE(interface){
    .def("get_array_that_owns_through_capsule", get_array_that_owns_through_capsule)

然后在while循环中,我可以整天调用此函数,并且

import interface
import psutil

def get_process_memory_usage():
    process = psutil.Process(os.getpid())
    return process.memory_info().rss
hundred_mb = 100000000
MEMORY_MAX = 100 * one_mb
i = 0
while True:
    print("PYTHON   : ---------------- While iteration ------------------- ({})".format(i))
    print("PYTHON   : BEFORE calling test_capsule_way()")
    arr = interface.get_array_that_owns_through_default_object()
    print("PYTHON   : AFTER calling test_capsule_way()")
    i += 1
    if i % 1000 == 0:
        print("PYTHON   : Nb arrays created (and pretty sure not destroyed) : {}".format(i))
        mem = get_process_memory_usage()
        if mem > MEMORY_MAX:
            print("PYTHON   : Bro chill with the memory, you're using {}MB over here!".format(mem/one_mb))
            quit()
    print("PYTHON   : ----------- End while iteration\n")

print("PYTHON   : SCRIPT END")

前几次迭代的输出是

PYTHON   : ---------------- While iteration ------------------- (0)
PYTHON   : BEFORE calling test_capsule_way()
C++      : boost::python::numpy::ndarray get_array_that_owns_through_capsule()data_ptr = 0x7fb7c9831010
PYTHON   : AFTER calling test_capsule_way()
PYTHON   : ----------- End while iteration
PYTHON   : ---------------- While iteration ------------------- (1)
PYTHON   : BEFORE calling test_capsule_way()
C++      : boost::python::numpy::ndarray get_array_that_owns_through_capsule()data_ptr = 0x7fb7c9746010
C++      : void destroyManagerCObject(PyObject*)  delete [] 0x7fb7c9831010
PYTHON   : AFTER calling test_capsule_way()
PYTHON   : ----------- End while iteration
PYTHON   : ---------------- While iteration ------------------- (2)
PYTHON   : BEFORE calling test_capsule_way()
C++      : boost::python::numpy::ndarray get_array_that_owns_through_capsule()data_ptr = 0x14c9f20
C++      : void destroyManagerCObject(PyObject*)  delete [] 0x7fb7c9746010
PYTHON   : AFTER calling test_capsule_way()
PYTHON   : ----------- End while iteration
PYTHON   : ---------------- While iteration ------------------- (3)

在本期中,我还演示了如何执行此操作,而不是使用胶囊,而是将boost::python::object()作为owner参数传递,不释放内存,并且python循环将由于检查过程存储器而停止:https://github.com/boostorg/python/issues/97#issuecomment-520555403