为何通过pybind11调用的C++函数执行速度慢于Python?
问题
听说C比Python快得多,于是用pybind11做了测试:分别实现C类Foo和Python类Bar,两者都包含自定义函数,用来计算0到100万整数的结果。测试代码如下:
测试代码
main.cpp
#include <pybind11/pybind11.h> #include <pybind11/functional.h> #include <pybind11/operators.h> #include <pybind11/stl.h> using namespace std; class Foo { private: function<double(int)> func; double result[1000000]; public: Foo(function<double(int)> f): func(f) {} void calculate() { for (int t=0; t<1000000; t++) { result[t] = func(t); } } }; PYBIND11_MODULE(cmake_example, m) { pybind11::class_<Foo>(m, "Foo") .def(pybind11::init<function<double(int)>>()) .def("calculate", &Foo::calculate); m.attr("__version__") = "0.0.1"; }
mytest.py
import time import cmake_example class Bar: def __init__(self, func): self.func = func self.result = [None for _ in range(1_000_000)] def calculate(self): for t in range(1_000_000): self.result[t] = self.func(t) def myfunction(t): return t * t if __name__ == "__main__": start = time.time() foo = cmake_example.Foo(myfunction) foo.calculate() print("cpp:", time.time() - start) start = time.time() bar = Bar(myfunction) bar.calculate() print("pyt:", time.time() - start)
测试结果
cpp: 0.4998281002044678 pyt: 0.39750099182128906
疑惑点:
- 为什么这个场景下C反而比Python慢?原本预期C更快。
- 更换其他数据类型能不能解决这个问题?
解答
核心原因:跨语言调用的开销
你看到的C更慢,核心问题出在**C循环里每次调用Python函数的跨语言开销**:
- 当C的
Foo::calculate循环调用func(t)时,这个func是Python函数,每次调用都需要完成:Python GIL的获取与释放、C到Python的类型转换(int转Python int)、Python函数调用栈的构建、返回值(Python int转C++ double)的转换,这些步骤的累加开销远大于Python内部调用函数的开销。 - 反观Python版本的
Bar.calculate,函数调用是在解释器内部完成的,没有跨语言的额外开销,而且CPython对内部函数调用和循环有不少优化,所以整体更快。 - 另外C++里的
std::function本身带有虚函数调用的额外开销,也会进一步拉低速度。
关于数据类型的疑问
更换数据类型几乎无法解决这个问题,因为性能瓶颈根本不是数据类型的计算速度,而是跨语言调用的额外开销——哪怕换用int或者其他类型,每次调用的类型转换、GIL操作这些开销依然存在。
优化方案
要让C++版本体现出速度优势,可以从以下几个方向调整:
- 把计算逻辑移到C++内部:
直接在C++里实现myfunction的逻辑,避免跨语言调用。比如修改Foo类,让它可以选择内置的计算函数:// 在C++里实现计算逻辑 double cpp_myfunction(int t) { return t * t; } // 给Foo添加内置计算选项 class Foo { // ... public: Foo() : func(cpp_myfunction) {} // 保留原有的构造函数,支持外部传入函数 Foo(function<double(int)> f): func(f) {} // ... }; - 减少跨语言调用次数:
不要循环调用Python函数,而是一次性把整个数据范围传给Python函数处理,或者让C++一次性处理所有数据,只做一次跨语言交互。 - 优化pybind11的函数调用方式:
使用py::function替代std::function,并手动提前获取GIL,避免循环内反复获取释放:void calculate() { py::gil_scoped_acquire acquire; // 提前获取GIL,循环内不再重复操作 for (int t=0; t<1000000; t++) { result[t] = func(t); } }
内容的提问来源于stack exchange,提问作者zchmielewska
相关产品推荐
相关产品推荐

