36

我有一个 C++ 函数计算一个大张量,我想通过pybind11作为 NumPy 数组返回 Python 。

从 pybind11 的文档来看,使用STL unique_ptr似乎是可取的。在下面的示例中,注释掉的版本有效,而给定的版本编译但在运行时失败(“无法将函数返回值转换为 Python 类型!”)。

为什么智能指针版本失败?创建和返回 NumPy 数组的规范方法是什么?

PS:由于程序结构和数组的大小,最好不要复制内存,而是从给定的指针创建数组。内存所有权应该由 Python 获得。

typedef typename py::array_t<double, py::array::c_style | py::array::forcecast> py_cdarray_t;

// py_cd_array_t _test()
std::unique_ptr<py_cdarray_t> _test()
{
    double * memory = new double[3]; memory[0] = 11; memory[1] = 12; memory[2] = 13;
    py::buffer_info bufinfo (
        memory,                                   // pointer to memory buffer
        sizeof(double),                           // size of underlying scalar type
        py::format_descriptor<double>::format(),  // python struct-style format descriptor
        1,                                        // number of dimensions
        { 3 },                                    // buffer dimensions
        { sizeof(double) }                        // strides (in bytes) for each index
    );

    //return py_cdarray_t(bufinfo);
    return std::unique_ptr<py_cdarray_t>( new py_cdarray_t(bufinfo) );
}
4

2 回答 2

68

一些评论(然后是一个有效的实现)。

  • pybind11 围绕 Python 类型(如pybind11::objectpybind11::list和,在本例中为pybind11::array_t<T>)的 C++ 对象包装器实际上只是围绕底层 Python 对象指针的包装器。在这方面,已经承担了共享指针包装器的角色,因此将其包装在 a 中毫无意义unique_ptrpy::array_t<T>直接返回对象本质上只是返回了一个美化的指针。
  • pybind11::array_t可以直接从数据指针构造,因此您可以跳过py::buffer_info中间步骤,只需将形状和步幅直接提供给pybind11::array_t构造函数。以这种方式构造的 numpy 数组不会拥有自己的数据,它只会引用它(即,numpyowndata标志将设置为 false)。
  • 内存所有权可以与 Python 对象的生命周期相关联,但您仍然需要正确执行释放操作。Pybind11 提供了一个py::capsule类来帮助你做到这一点。您要做的是通过将 numpy 数组指定basearray_t. 这将使 numpy 数组引用它,只要数组本身还活着,它就会保持活动状态,并在不再引用它时调用清理函数。
  • 旧版本(2.2 之前)中的c_style标志只对新数组有影响,即不传递值指针时。如果您仅指定形状但不指定步幅,则该问题在 2.2 版本中已修复,也会影响自动步幅。如果您自己直接指定步幅(就像我在下面的示例中所做的那样),它根本没有效果。

因此,将这些部分放在一起,这段代码是一个完整的 pybind11 模块,它演示了如何完成您正在寻找的内容(并包含一些 C++ 输出以证明确实可以正常工作):

#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

PYBIND11_PLUGIN(numpywrap) {
    py::module m("numpywrap");
    m.def("f", []() {
        // Allocate and initialize some data; make this big so
        // we can see the impact on the process memory use:
        constexpr size_t size = 100*1000*1000;
        double *foo = new double[size];
        for (size_t i = 0; i < size; i++) {
            foo[i] = (double) i;
        }

        // Create a Python object that will free the allocated
        // memory when destroyed:
        py::capsule free_when_done(foo, [](void *f) {
            double *foo = reinterpret_cast<double *>(f);
            std::cerr << "Element [0] = " << foo[0] << "\n";
            std::cerr << "freeing memory @ " << f << "\n";
            delete[] foo;
        });

        return py::array_t<double>(
            {100, 1000, 1000}, // shape
            {1000*1000*8, 1000*8, 8}, // C-style contiguous strides for double
            foo, // the data pointer
            free_when_done); // numpy array references this parent
    });
    return m.ptr();
}

编译它并从 Python 调用它表明它可以工作:

>>> import numpywrap
>>> z = numpywrap.f()
>>> # the python process is now taking up a bit more than 800MB memory
>>> z[1,1,1]
1001001.0
>>> z[0,0,100]
100.0
>>> z[99,999,999]
99999999.0
>>> z[0,0,0] = 3.141592
>>> del z
Element [0] = 3.14159
freeing memory @ 0x7fd769f12010
>>> # python process memory size has dropped back down
于 2017-06-21T17:28:49.467 回答
0

我建议使用ndarray。一个基本原则是,除非明确要求,否则永远不会复制基础数据(或者您很快就会以极大的低效率告终)。下面是它的一个使用示例,但还有其他功能我没有展示,包括转换为特征数组 ( ndarray::asEigen(array)),这使得它非常强大。

标题:

#ifndef MYTENSORCODE_H
#define MYTENSORCODE_H

#include "ndarray_fwd.h"

namespace myTensorNamespace {

ndarray::Array<double, 2, 1> myTensorFunction(int param1, double param2);

}  // namespace myTensorNamespace

#endif  // include guard

库:

#include "ndarray.h"
#include "myTensorCode.h"

namespace myTensorNamespace {

ndarray::Array<double, 2, 1> myTensorFunction(int param1, double param2) {
    std::size_t const size = calculateSize();
    ndarray::Array<double, 2, 1> array = ndarray::allocate(size, size);
    array.deep() = 0;  // initialise
    for (std::size_t ii = 0; ii < size; ++ii) {
        array[ii][ndarray::view(ii, ii + 1)] = 1.0;
    }
    return array;
}

}  // namespace myTensorNamespace

包装:

#include "pybind11/pybind11.h"
#include "ndarray.h"
#include "ndarray/pybind11.h"

#include "myTensorCode.h"

namespace py = pybind11;
using namespace pybind11::literals;

namespace myTensorNamespace {
namespace {

PYBIND11_MODULE(myTensorModule, mod) {
    mod.def("myTensorFunction", &myTensorFunction, "param1"_a, "param2"_a);
}

}  // anonymous namespace
}  // namespace myTensorNamespace
于 2021-10-19T21:12:47.920 回答