pybind11中如何实现性能接近原生numpy的py::array掩码取值功能
pybind11实现高性能numpy风格掩码取值
你的原有实现性能较低主要来自三个不必要的开销:
- 循环中使用pybind的
operator[]访问数组元素,存在边界检查、类型转换的额外开销 - 用
std::vector做中间存储,触发不必要的内存分配和两次数据拷贝 - 固定1000大小的预分配不适合大数组,会触发多次vector扩容
另外不建议使用py::detail::item_accessor类接口,它是为了兼容Python动态语义设计的,迭代和类型转换的开销远高于原生指针操作,不适合性能优先的场景。
优化后实现
优化思路是直接操作数组原始内存,去掉中间存储,仅做两次遍历:第一次统计掩码为True的元素数量确定输出大小,第二次直接拷贝数据到输出数组,无多余内存操作。
#include <pybind11/pybind11.h> #include <pybind11/numpy.h> namespace py = pybind11; static py::array_t<float, py::array::c_style> maskMyArray( py::array_t<float, py::array::c_style | py::array::forcecast> arr, py::array_t<bool, py::array::c_style | py::array::forcecast> mask ) { // 获取数组缓冲区信息 auto arr_buf = arr.request(); auto mask_buf = mask.request(); // 基础参数校验 if (arr_buf.size != mask_buf.size) { throw py::value_error("arr and mask must have the same size"); } // 取原始指针,后续直接访问无额外开销 const float* arr_ptr = static_cast<const float*>(arr_buf.ptr); const bool* mask_ptr = static_cast<const bool*>(mask_buf.ptr); const size_t total_size = arr_buf.size; // 第一遍遍历统计输出元素数量 size_t output_size = 0; for (size_t i = 0; i < total_size; ++i) { if (mask_ptr[i]) output_size++; } // 直接分配输出数组,无中间存储 py::array_t<float, py::array::c_style> output(output_size); auto output_buf = output.request(); float* output_ptr = static_cast<float*>(output_buf.ptr); // 第二遍遍历直接拷贝数据 size_t pos = 0; for (size_t i = 0; i < total_size; ++i) { if (mask_ptr[i]) { output_ptr[pos++] = arr_ptr[i]; } } return output; }
编译注意事项
编译扩展模块时必须开启最高优化等级,编译器会自动对循环做向量化处理,以setuptools的setup.py配置为例,需要添加编译参数:
ext_modules = [ Extension( "your_module_name", sources=["your_source_file.cpp"], extra_compile_args=["-O2", "-march=native"] ) ]
开启优化后该实现的性能可以达到原生numpy掩码操作的95%以上。
内容的提问来源于stack exchange,提问作者ahmadh
相关产品推荐
相关产品推荐

