返回多个 py::array 而不在 pybind11 中复制
returning multiple py::array without copying in pybind11
我正在尝试使用 pybind11 在 C++ 中构建一个 python 模块。我有以下代码:
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
struct ContainerElement
{
uint8_t i;
double d;
double d2;
};
class Container
{
private:
std::vector<uint8_t> ints;
std::vector<double> doubles;
std::vector<double> doubles2;
public:
std::vector<uint8_t>& getInts() { return ints; }
std::vector<double>& getDoubles() { return doubles; }
std::vector<double>& getDoubles2() { return doubles2; }
void addElement(ContainerElement element)
{
ints.emplace_back(element.i);
doubles.emplace_back(element.d);
doubles2.emplace_back(element.d2);
}
};
void fillContainer(Container& container)
{
for (int i = 0; i < 1e6; ++i)
{
container.addElement({(uint8_t)i, (double)i,(double)i });
}
}
PYBIND11_MODULE(containerInterface, m) {
py::class_<Container>(m, "Container")
.def(py::init<>())
.def("getInts", [](Container& container)
{
return py::array_t<uint8_t>(
{ container.getInts().size() },
{ sizeof(uint8_t) },
container.getInts().data());
})
.def("getDoubles", [](Container& container)
{
return py::array_t<double>(
{ container.getDoubles().size() },
{ sizeof(double) },
container.getDoubles().data());
})
.def("getDoubles2", [](Container& container)
{
return py::array_t<double>(
{ container.getDoubles2().size() },
{ sizeof(double) },
container.getDoubles2().data());
});
m.def("fillContainer", &fillContainer);
}
当我在 python 中调用此代码时:
import containerInterface
container = containerInterface.Container()
containerInterface.fillContainer(container)
i = container.getInts()
d = container.getDoubles()
d2 = container.getDoubles2()
这行得通,但是当我检查程序的内存使用情况(使用 psutil.Process(os.getpid()).memory_info().rss
)时,它似乎在我调用函数 getInts, getDoubles
和 getDoubles2
时制作了一个副本。有没有办法避免这种情况?
我试过使用np.array(container.getInts(), copy=False)
,但它仍然会生成副本。我还尝试在容器 class 上使用 py::buffer_protocol()
,如此处所述: https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html 。但是,我只能使它适用于 Ints 向量或 Doubles 向量,而不是同时适用于所有向量。
PYBIND11_MODULE(containerInterface, m) {
py::class_<Container>(m, "Container", py::buffer_protocol())
.def(py::init<>())
.def("getInts", &Container::getInts)
.def("getDoubles", &Container::getDoubles)
.def_buffer([](Container& container) -> py::buffer_info {
return py::buffer_info(
container.getInts().data(),
sizeof(uint8_t),
py::format_descriptor<uint8_t>::format(),
1,
{ container.getInts().size() },
{ sizeof(uint8_t) * container.getInts().size() }
);
});
m.def("fillContainer", &fillContainer);
然后我可以使用 i = np.array(container, copy=False)
,而无需制作副本。然而,正如我所说,它现在只适用于 Ints
向量。
我猜您必须指定访问函数 return 引用而不是副本,这可能是默认设置。我不知道你是如何用 pybind 做到这一点的,但我已经用 boost::python 和 Ponder.
做到了
即您需要指定 return 政策 .
我找到了一个有效的解决方案。虽然它可能不是最优雅的。我创建了三个新的 类 Ints
、Doubles
和 Doubles2
,它们采用原始容器并通过函数调用 getValues()
公开各自的向量。使用这三个 类 我可以为所有 类.
指定三次缓冲区协议
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <pybind11/buffer_info.h>
namespace py = pybind11;
struct ContainerElement
{
uint8_t i;
double d;
double d2;
};
class Container
{
private:
std::vector<uint8_t> ints;
std::vector<double> doubles;
std::vector<double> doubles2;
public:
std::vector<uint8_t>& getInts() { return ints; }
std::vector<double>& getDoubles() { return doubles; }
std::vector<double>& getDoubles2() { return doubles2; }
void addElement(ContainerElement element)
{
ints.emplace_back(element.i);
doubles.emplace_back(element.d);
doubles2.emplace_back(element.d2);
}
};
void fillContainer(Container& container)
{
for (int i = 0; i < 1e6; ++i)
{
container.addElement({ (uint8_t)i, (double)i,(double)i });
}
}
class Ints
{
private:
Container& cont;
public:
Ints(Container& cont) : cont(cont) {}
std::vector<uint8_t>& getValues() { return cont.getInts(); }
};
class Doubles
{
private:
Container& cont;
public:
Doubles(Container& cont) : cont(cont) {}
std::vector<double>& getValues() { return cont.getDoubles(); }
};
class Doubles2
{
private:
Container& cont;
public:
Doubles2(Container& cont) : cont(cont) {}
std::vector<double>& getValues() { return cont.getDoubles2(); }
};
PYBIND11_MODULE(newInterface, m) {
py::class_<Container>(m, "Container")
.def(py::init<>());
py::class_<Ints>(m, "Ints", py::buffer_protocol())
.def(py::init<Container&>(), py::keep_alive<1, 2>())
.def_buffer([](Ints& ints) -> py::buffer_info {
return py::buffer_info(
ints.getValues().data(),
sizeof(uint8_t),
py::format_descriptor<uint8_t>::format(),
ints.getValues().size()
);
});
py::class_<Doubles>(m, "Doubles", py::buffer_protocol())
.def(py::init<Container&>(), py::keep_alive<1, 2>())
.def_buffer([](Doubles& doubles) -> py::buffer_info {
return py::buffer_info(
doubles.getValues().data(),
sizeof(double),
py::format_descriptor<double>::format(),
doubles.getValues().size()
);
});
py::class_<Doubles2>(m, "Doubles2", py::buffer_protocol())
.def(py::init<Container&>(), py::keep_alive<1, 2>())
.def_buffer([](Doubles2& doubles2) -> py::buffer_info {
return py::buffer_info(
doubles2.getValues().data(),
sizeof(double),
py::format_descriptor<double>::format(),
doubles2.getValues().size()
);
});
m.def("fillContainer", &fillContainer);
}
这样我就可以在Python中按以下方式使用代码:
import newInterface as ci
import numpy as np
container = ci.Container()
ci.fillContainer(container)
i = np.array(ci.Ints(container), copy=False)
d = np.array(ci.Doubles(container), copy=False)
d2 = np.array(ci.Doubles2(container), copy=False)
一旦 fillContainer
填满容器,numpy 数组的构造就不会从该容器复制值。
这并没有直接解决问题,但仍然允许在不进行复制的情况下返回数组缓冲区。
灵感来自这个线程:
https://github.com/pybind/pybind11/issues/1042
基本上,只需向 py::array() 构造函数提供一个 py::capsule。
有了这个,py::array() 构造函数 而不是 分配一个单独的缓冲区和副本。例如:
// Use this if the C++ buffer should NOT be deallocated
// once Python no longer has a reference to it
py::capsule buffer_handle([](){});
// Use this if the C++ buffer SHOULD be deallocated
// once the Python no longer has a reference to it
// py::capsule buffer_handle(data_buffer, [](void* p){ free(p); });
return py::array(py::buffer_info(
data_buffer,
element_size,
data_type,
dims_length,
dims,
strides
), buffer_handle);
我正在尝试使用 pybind11 在 C++ 中构建一个 python 模块。我有以下代码:
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
struct ContainerElement
{
uint8_t i;
double d;
double d2;
};
class Container
{
private:
std::vector<uint8_t> ints;
std::vector<double> doubles;
std::vector<double> doubles2;
public:
std::vector<uint8_t>& getInts() { return ints; }
std::vector<double>& getDoubles() { return doubles; }
std::vector<double>& getDoubles2() { return doubles2; }
void addElement(ContainerElement element)
{
ints.emplace_back(element.i);
doubles.emplace_back(element.d);
doubles2.emplace_back(element.d2);
}
};
void fillContainer(Container& container)
{
for (int i = 0; i < 1e6; ++i)
{
container.addElement({(uint8_t)i, (double)i,(double)i });
}
}
PYBIND11_MODULE(containerInterface, m) {
py::class_<Container>(m, "Container")
.def(py::init<>())
.def("getInts", [](Container& container)
{
return py::array_t<uint8_t>(
{ container.getInts().size() },
{ sizeof(uint8_t) },
container.getInts().data());
})
.def("getDoubles", [](Container& container)
{
return py::array_t<double>(
{ container.getDoubles().size() },
{ sizeof(double) },
container.getDoubles().data());
})
.def("getDoubles2", [](Container& container)
{
return py::array_t<double>(
{ container.getDoubles2().size() },
{ sizeof(double) },
container.getDoubles2().data());
});
m.def("fillContainer", &fillContainer);
}
当我在 python 中调用此代码时:
import containerInterface
container = containerInterface.Container()
containerInterface.fillContainer(container)
i = container.getInts()
d = container.getDoubles()
d2 = container.getDoubles2()
这行得通,但是当我检查程序的内存使用情况(使用 psutil.Process(os.getpid()).memory_info().rss
)时,它似乎在我调用函数 getInts, getDoubles
和 getDoubles2
时制作了一个副本。有没有办法避免这种情况?
我试过使用np.array(container.getInts(), copy=False)
,但它仍然会生成副本。我还尝试在容器 class 上使用 py::buffer_protocol()
,如此处所述: https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html 。但是,我只能使它适用于 Ints 向量或 Doubles 向量,而不是同时适用于所有向量。
PYBIND11_MODULE(containerInterface, m) {
py::class_<Container>(m, "Container", py::buffer_protocol())
.def(py::init<>())
.def("getInts", &Container::getInts)
.def("getDoubles", &Container::getDoubles)
.def_buffer([](Container& container) -> py::buffer_info {
return py::buffer_info(
container.getInts().data(),
sizeof(uint8_t),
py::format_descriptor<uint8_t>::format(),
1,
{ container.getInts().size() },
{ sizeof(uint8_t) * container.getInts().size() }
);
});
m.def("fillContainer", &fillContainer);
然后我可以使用 i = np.array(container, copy=False)
,而无需制作副本。然而,正如我所说,它现在只适用于 Ints
向量。
我猜您必须指定访问函数 return 引用而不是副本,这可能是默认设置。我不知道你是如何用 pybind 做到这一点的,但我已经用 boost::python 和 Ponder.
做到了即您需要指定 return 政策 .
我找到了一个有效的解决方案。虽然它可能不是最优雅的。我创建了三个新的 类 Ints
、Doubles
和 Doubles2
,它们采用原始容器并通过函数调用 getValues()
公开各自的向量。使用这三个 类 我可以为所有 类.
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <pybind11/buffer_info.h>
namespace py = pybind11;
struct ContainerElement
{
uint8_t i;
double d;
double d2;
};
class Container
{
private:
std::vector<uint8_t> ints;
std::vector<double> doubles;
std::vector<double> doubles2;
public:
std::vector<uint8_t>& getInts() { return ints; }
std::vector<double>& getDoubles() { return doubles; }
std::vector<double>& getDoubles2() { return doubles2; }
void addElement(ContainerElement element)
{
ints.emplace_back(element.i);
doubles.emplace_back(element.d);
doubles2.emplace_back(element.d2);
}
};
void fillContainer(Container& container)
{
for (int i = 0; i < 1e6; ++i)
{
container.addElement({ (uint8_t)i, (double)i,(double)i });
}
}
class Ints
{
private:
Container& cont;
public:
Ints(Container& cont) : cont(cont) {}
std::vector<uint8_t>& getValues() { return cont.getInts(); }
};
class Doubles
{
private:
Container& cont;
public:
Doubles(Container& cont) : cont(cont) {}
std::vector<double>& getValues() { return cont.getDoubles(); }
};
class Doubles2
{
private:
Container& cont;
public:
Doubles2(Container& cont) : cont(cont) {}
std::vector<double>& getValues() { return cont.getDoubles2(); }
};
PYBIND11_MODULE(newInterface, m) {
py::class_<Container>(m, "Container")
.def(py::init<>());
py::class_<Ints>(m, "Ints", py::buffer_protocol())
.def(py::init<Container&>(), py::keep_alive<1, 2>())
.def_buffer([](Ints& ints) -> py::buffer_info {
return py::buffer_info(
ints.getValues().data(),
sizeof(uint8_t),
py::format_descriptor<uint8_t>::format(),
ints.getValues().size()
);
});
py::class_<Doubles>(m, "Doubles", py::buffer_protocol())
.def(py::init<Container&>(), py::keep_alive<1, 2>())
.def_buffer([](Doubles& doubles) -> py::buffer_info {
return py::buffer_info(
doubles.getValues().data(),
sizeof(double),
py::format_descriptor<double>::format(),
doubles.getValues().size()
);
});
py::class_<Doubles2>(m, "Doubles2", py::buffer_protocol())
.def(py::init<Container&>(), py::keep_alive<1, 2>())
.def_buffer([](Doubles2& doubles2) -> py::buffer_info {
return py::buffer_info(
doubles2.getValues().data(),
sizeof(double),
py::format_descriptor<double>::format(),
doubles2.getValues().size()
);
});
m.def("fillContainer", &fillContainer);
}
这样我就可以在Python中按以下方式使用代码:
import newInterface as ci
import numpy as np
container = ci.Container()
ci.fillContainer(container)
i = np.array(ci.Ints(container), copy=False)
d = np.array(ci.Doubles(container), copy=False)
d2 = np.array(ci.Doubles2(container), copy=False)
一旦 fillContainer
填满容器,numpy 数组的构造就不会从该容器复制值。
这并没有直接解决问题,但仍然允许在不进行复制的情况下返回数组缓冲区。 灵感来自这个线程: https://github.com/pybind/pybind11/issues/1042
基本上,只需向 py::array() 构造函数提供一个 py::capsule。 有了这个,py::array() 构造函数 而不是 分配一个单独的缓冲区和副本。例如:
// Use this if the C++ buffer should NOT be deallocated
// once Python no longer has a reference to it
py::capsule buffer_handle([](){});
// Use this if the C++ buffer SHOULD be deallocated
// once the Python no longer has a reference to it
// py::capsule buffer_handle(data_buffer, [](void* p){ free(p); });
return py::array(py::buffer_info(
data_buffer,
element_size,
data_type,
dims_length,
dims,
strides
), buffer_handle);