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

时间:2019-02-25 23:34:07

标签: python c++ numpy c++11 pybind11

我有一个返回std::vector的C ++函数,并且使用Pybind11,我想将该矢量的内容作为Numpy数组返回,而不必将矢量的基础数据复制到原始数据数组中

当前尝试

this well-written SO answer中,作者演示了当Numpy数组的引用计数为零时,如何确保适当释放用C ++创建的原始数据数组。我尝试使用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);
}

已观察到的问题

但是,如果我从Python调用它,我会观察到两件事:(1)输出数组中的数据是垃圾,(2)手动删除Numpy数组时,我收到以下错误(SIGABRT):

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

我的猜测是,此问题与“ delete[] foo”行有关,大概是在将foo设置为result.data()的情况下调用的。这不是解除分配std::vector的方法。

可能的解决方案

一种可能的解决方案是创建一个T *ptr = new T[result.size()]并将result的内容复制到此原始数据数组。但是,在某些情况下,结果可能会很大,因此我想避免花费所有时间来分配和复制。 (但也许没有我想的那么长。)

我对std::allocator也不了解,但是也许有一种方法可以分配compute_something_returns_vector()函数调用的输出向量 outside 所需的原始数据数组然后丢弃std::vector,然后保留基础原始数据数组?

最后的选择是重写compute_something_returns_vector

1 个答案:

答案 0 :(得分:2)

  

与同事进行离线讨论后,我解决了我的问题。我不想冒充别人,所以我不会接受自己的回答。但是,为了将SO用作信息目录,我想在这里为其他人提供答案。

问题很简单:result是堆栈分配的,需要进行堆分配,以便free_when_done可以拥有所有权。以下是修复示例:

{
    // ... 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);
}

我还能够使用std::unique_ptr实现解决方案,而无需使用free_when_done函数。但是,我无法使用任何一种解决方案运行Valgrind,因此我不能100%确定该向量所保存的内存是否已适当释放。 (Valgrind + Python对我来说是个谜。)为完整起见,下面是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
}

但是,我能够检查在Python和C ++代码中分配的向量的地址,并确认没有复制compute_something_returns_vector()的输出。