简体   繁体   English

Pybind11和std :: vector —如何使用胶囊释放数据?

[英]Pybind11 and std::vector — How to free data using capsules?

I have a C++ function that returns a std::vector and, using Pybind11, I would like to return the contents of that vector as a Numpy array without having to copy the underlying data of the vector into a raw data array. 我有一个C ++函数,它返回一个std::vector并且使用Pybind11,我想将该向量的内容作为Numpy数组返回,而不必将向量的基础数据复制到原始数据数组中。

Current Attempt 当前尝试

In this well-written SO answer the author demonstrates how to ensure that a raw data array created in C++ is appropriately freed when the Numpy array has zero reference count. 这个写得很好的SO答案中 ,作者演示了当Numpy数组的引用计数为零时,如何确保适当释放在C ++中创建的原始数据数组。 I tried to write a version of this using std::vector instead: 我尝试使用std::vector来编写此版本:

// aside - I made a templated version of the wrapper with which
// I create specific instances of in the PYBIND11_MODULE definitions:
//
//     m.def("my_func", &wrapper<int>, ...)
//     m.def("my_func", &wrapper<float>, ...)
// 
template <typename T>
py::array_t<T> wrapper(py::array_t<T> input) {
    auto proxy = input.template unchecked<1>();
    std::vector<T> result = compute_something_returns_vector(proxy);

    // give memory cleanup responsibility to the Numpy array
    py::capsule free_when_done(result.data(), [](void *f) {
        auto foo = reinterpret_cast<T  *>(f);
        delete[] foo;
    });

    return py::array_t<T>({result.size()}, // shape
                          {sizeof(T)},     // stride
                          result.data(),   // data pointer
                          free_when_done);
}

Observed Issues 观察到的问题

However, if I call this from Python I observe two things: (1) the data in the output array is garbage and (2) when I manually delete the Numpy array I receive the following error (SIGABRT): 但是,如果我从Python调用它,我会观察到两件事:(1)输出数组中的数据是垃圾,(2)手动删除Numpy数组时,我收到以下错误(SIGABRT):

python3(91198,0x7fff9f2c73c0) malloc: *** error for object 0x7f8816561550: pointer being freed was not allocated

My guess is that this issue has to do with the line " delete[] foo ", which presumably is being called with foo set to result.data() . 我的猜测是,此问题与“ delete[] foo ”行有关,大概是通过将foo设置为result.data()result.data() This is not the way to deallocate a std::vector . 这不是释放std::vector

Possible Solutions 可能的解决方案

One possible solution is to create a T *ptr = new T[result.size()] and copy the contents of result to this raw data array. 一种可能的解决方案是创建一个T *ptr = new T[result.size()]并将result内容复制到此原始数据数组。 However, I have cases where the results might be large and I want to avoid taking all of that time to allocate and copy. 但是,在某些情况下,结果可能会很大,因此我想避免花费所有时间来分配和复制。 (But perhaps it's not as long as I think it would be.) (但是也许没有我想的那么长。)

Also, I don't know much about std::allocator but perhaps there is a way to allocate the raw data array needed by the output vector outside the compute_something_returns_vector() function call and then discard the std::vector afterwards, retaining the underlying raw data array? 另外,我不是很了解std::allocator ,但也许有分配由输出载体所需要的原始数据阵的方式compute_something_returns_vector()函数调用,然后丢弃std::vector事后,保留底层原始数据数组?

The final option is to rewrite compute_something_returns_vector . 最后的选择是重写compute_something_returns_vector

After an offline discussion with a colleague I resolved my problem. 与同事进行离线讨论后,我解决了我的问题。 I do not want to commit an SO faux pas so I won't accept my own answer. 我不想冒充别人,所以我不会接受自己的回答。 However, for the sake of using SO as a catalog of information I want to provide the answer here for others. 但是,为了将SO用作信息目录,我想在此为其他人提供答案。

The problem was simple: result was stack-allocated and needed to be heap-allocated so that free_when_done can take ownership. 问题很简单: result是堆栈分配的,需要进行堆分配,以便free_when_done可以拥有所有权。 Below is an example fix: 以下是修复示例:

{
    // ... snip ...

    std::vector<T> *result = new std::vector<T>(compute_something_returns_vector(proxy));

    py::capsule free_when_done(result, [](void *f) {
      auto foo = reinterpret_cast<std::vector<T> *>(f);
      delete foo;
    });

    return py::array_t<T>({result->size()}, // shape
                          {sizeof(T)},      // stride
                          result->data(),   // data pointer
                          free_when_done);
}

I was also able to implement a solution using std::unique_ptr that doesn't require the use of a free_when_done function. 我还能够使用std::unique_ptr实现解决方案,而无需使用free_when_done函数。 However, I wasn't able to run Valgrind with either solution so I'm not 100% sure that the memory held by the vector was appropriately freed. 但是,我无法使用任何一种解决方案运行Valgrind,因此我不能100%确定该向量所保存的内存是否已适当释放。 (Valgrind + Python is a mystery to me.) For completeness, below is the std::unique_ptr approach: (Valgrind + Python对我来说是个谜。)为完整std::unique_ptr ,以下是std::unique_ptr方法:

{
    // ... snip ...

    std::unique_ptr<std::vector<T>> result =
        std::make_unique<std::vector<T>>(compute_something_returns_vector(proxy));

    return py::array_t<T>({result->size()}, // shape
                          {sizeof(T)},      // stride
                          result->data());  // data pointer
}

I was, however, able to inspect the addresses of the vectors allocated in both the Python and C++ code and confirmed that no copies of the output of compute_something_returns_vector() were made. 但是,我能够检查在Python和C ++代码中分配的向量的地址,并确认没有对compute_something_returns_vector()的输出进行任何复制。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM