如何缩短 pybind11 扩展项目的编译构建时间?
【免费下载链接】pybind11Seamless operability between C++11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11
用 pybind11 编写 C++/Python 扩展模块时,绑定代码一旦增多,编译就会成为明显的耗时环节:既拖慢单次全量构建,也拖慢每次改动后的增量构建。pybind11 官方文档给出的缩短编译时间的做法主要是三件事:把绑定代码拆分到多个文件以便并行编译,给 setuptools 项目启用多线程编译,以及在开发阶段跳过不必要的重编。下面的做法均按你项目实际使用的构建方式(setuptools 或 CMake)选择对应章节即可。
把绑定代码拆分成多个文件
docs/faq.rst 中 "How can I reduce the build time?" 一节给出的建议是:将绑定代码拆分到多个文件中,让各个编译单元相互独立地编译,最后链接成同一个扩展模块。官方示例的结构如下:
example.cpp中只声明并调用各初始化函数:
void init_ex1(py::module_ &); void init_ex2(py::module_ &); /* ... */ PYBIND11_MODULE(example, m, py::mod_gil_not_used()) { init_ex1(m); init_ex2(m); /* ... */ }ex1.cpp:
void init_ex1(py::module_ &m) { m.def("add", [](int a, int b) { return a + b; }); }ex2.cpp:
void init_ex2(py::module_ &m) { m.def("sub", [](int a, int b) { return a - b; }); }各init_ex函数应放在彼此独立、可单独编译的文件中,再链接到同一个最终共享库。文档说明这样做带来三个效果:
- 降低单个编译单元的内存需求;
- 允许并行构建(如需要时);
- 支持更快的增量构建,例如当某个类定义变化时,通常只需要重新编译绑定代码的一个子集。
模块构建完成后,可以按文档示例在 Python 中验证拆分后的模块行为是否与原模块一致(以下为文档示例输出):
>>> import example >>> example.add(1, 2) 3 >>> example.sub(1, 1) 0拆分的粒度决定并行的上限:文件拆得越细,可独立编译的编译单元越多,并行编译和增量编译的收益越大。
在 setuptools 项目中启用并行编译
如果项目用 setuptools 构建,pybind11 自带一个不依赖 NumPy 的并行编译工具,是 NumPy 并行编译工具的轻量替代品。在setup.py中这样使用:
from pybind11.setup_helpers import ParallelCompile # Optional multithreaded build ParallelCompile("NPY_NUM_BUILD_JOBS").install() setup(...)ParallelCompile的构造函数接受以下参数(pybind11/setup_helpers.py中的 docstring 说明):
envvar:控制编译线程数的环境变量名,如NPY_NUM_BUILD_JOBS(NumPy 使用的名字),也可以换成CMAKE_BUILD_PARALLEL_LEVEL这类用户更容易预期的名字;default:默认线程数,0表示自动使用可用线程数,1表示只有设置了环境变量时才多线程;max:自动多线程时的线程数上限。文档建议对于较大的扩展模块,可以把这个值设为与内存相关的数值。
注意上面的setup(...)表示保持你项目原有的setup()调用不变,只需在它之前插入ParallelCompile(...).install()这一行;setup的参数不在本文讨论范围。
快速开发时用增量重编或 Ccache 跳过重建
文档针对"开发迭代快、C++ 文件多"的场景给出了两条路径,二选一即可。
第一条是使用naive_recompile,在pip install -e .(editable 模式)且项目没有本地头文件时,如果目标文件比源文件新就跳过重建:
from pybind11.setup_helpers import ParallelCompile, naive_recompile ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install()它的判定依据是目标文件(obj)与源文件(src)的时间戳,不检查头文件是否有变化——这是文档明确标注的限制,如果你的包里有可编辑的头文件,就需要实现一个更智能的函数传给needs_recompile,或者改用 Ccache。
第二条是 Ccache,适用于更复杂的构建。以 GCC 为例:
CXX="cache g++" pip install -e .使用 Ccache 的前提是系统已安装 Ccache。与naive_recompile不同,它在非 editable 模式的编译下也能生效。
另有一个文档提示的现象需要留意:Pip 认为代码已经构建过时(它依据版本号判断)会直接跳过重建。如果你改了代码却发现没有重新编译,可以清除缓存,或使用 Pip 的--no-cache-dir选项强制重新构建;另一种做法是使用setuptools_scm,它生成的版本号包含自上一个 tag 以来的提交数和脏目录的哈希,能避免 Pip 误判。
CMake 项目:并行构建
如果项目走 CMake 路线,pybind11 文档给出的构建流程如下(CMake 3.15+ 写法),其中-j 2表示用 2 个核心并行构建,可按机器核心数调整:
cd pybind11 cmake -S . -B build cmake --build build -j 2 # Build on 2 cores cmake --install buildfind_package(pybind11)需要 pybind11 已经安装到系统上,上面的流程就是官方文档给出的安装步骤。
适用边界
以上各条对应不同构建方式:setuptools 章节的ParallelCompile、naive_recompile、Ccache 与 pip 缓存只适用于 setuptools 路线;CMake 章节的-j并行构建适用于 CMake 路线。两条路线是并列的替代方案,按项目实际使用的构建系统选择一条执行即可,不要混用。拆分绑定文件的做法对两种构建方式都适用,通常可以先做这一步,再叠加对应路线的并行与缓存手段。
【免费下载链接】pybind11Seamless operability between C++11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考