ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

如何缩短 pybind11 扩展项目的编译构建时间?

如何缩短 pybind11 扩展项目的编译构建时间? 如何缩短 pybind11 扩展项目的编译构建时间【免费下载链接】pybind11Seamless operability between C11 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.cppvoid init_ex1(py::module_ m) { m.def(add, [](int a, int b) { return a b; }); }ex2.cppvoid 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_JOBSNumPy 使用的名字也可以换成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_recompilenaive_recompile).install()它的判定依据是目标文件obj与源文件src的时间戳不检查头文件是否有变化——这是文档明确标注的限制如果你的包里有可编辑的头文件就需要实现一个更智能的函数传给needs_recompile或者改用 Ccache。第二条是 Ccache适用于更复杂的构建。以 GCC 为例CXXcache 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 C11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进