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

Chr*_*ski 7 c++ python numpy c++11 pybind11

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

当前尝试

这个写得很好的 SO 答案中,作者演示了如何确保在 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);
}
Run Code Online (Sandbox Code Playgroud)

观察到的问题

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

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

我的猜测是这个问题与行“ delete[] foo”有关,大概是用fooset to调用的result.data()。这不是解除分配 a 的方法std::vector

可能的解决方案

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

另外,我不太了解,std::allocator但也许有一种方法可以在函数调用之外分配输出向量所需的原始数据数组compute_something_returns_vector(),然后丢弃它std::vector,保留底层原始数据数组?

最后的选择是重写compute_something_returns_vector.

Chr*_*ski 9

在与同事进行离线讨论后,我解决了我的问题。我不想犯这样的失礼,所以我不会接受我自己的答案。但是,为了使用 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);
}
Run Code Online (Sandbox Code Playgroud)

我还能够使用std::unique_ptr不需要使用free_when_done函数的解决方案来实现。但是,我无法使用任一解决方案运行 Valgrind,因此我不能 100% 确定向量所持有的内存已被适当释放。(Valgrind + Python 对我来说是个谜。)为了完整起见,下面是std::unique_ptr方法:

{
    // ... 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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 不用担心,接受自己的答案很好。与您的第一个类似的示例也在这里:https://github.com/pybind/pybind11/issues/1042#issuecomment-325941022 第二个(带有 unique_ptr)在我看来不正确,数据将当你从这个函数返回时被释放。 (2认同)