Pybind11 is slower than Pure Python



我使用pybind11创建了Python绑定。一切都很顺利,但当我做speed check test时,结果令人失望。

基本上,我在C++中有一个函数,它添加了两个数字,我想使用Python脚本中的那个函数。我还包含了一个运行100次的for循环,以更好地查看处理时间的差异。

对于函数";导入的";从C++中,使用pybind11,我获得了:0.002310514450073242 ~ 0.0034799575805664062

对于简单的Python脚本,我获得了:0.0012788772583007812 ~ 0.0015883445739746094

main.cpp文件:

#include <pybind11/pybind11.h>
namespace py = pybind11;
double sum(double a, double b) {
return a + b;
}
PYBIND11_MODULE(SumFunction, var) {
var.doc() = "pybind11 example module";
var.def("sum", &sum, "This function adds two input numbers");
}

main.py文件:

from build.SumFunction import *
import time
start = time.time()
for i in range(100):
print(sum(2.3,5.2))
end = time.time()
print(end - start)

CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.0.0)
project(Projectpybind11 VERSION 0.1.0)
include(CTest)
enable_testing()
add_subdirectory(pybind11)
pybind11_add_module(SumFunction main.cpp)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

简单Python脚本:

import time
def summ(a,b):
return a+b
start = time.time()
for i in range(100):
print(summ(2.3,5.2))
end = time.time()
print(end - start)
  1. 基准测试是一件非常复杂的事情,甚至可以称为系统工程

    因为有很多流程会干扰我们的基准测试工作。对于示例:NIC中断响应/键盘或鼠标输入/OS调度。。。我遇到我的生产过程被操作系统阻止长达15秒!因此,正如其他顾问所指出的,print()调用了更多不必要的干扰。

  2. 你的测试计算太简单了。

    你必须想清楚你拿什么做比较。Python和C++之间传递参数的速度显然比Python内部的。所以我假设你想比较一下两者的速度,而不是参数传递速度。如果是这样的话,我认为你的计算代码太简单了,这将导致我们统计的时间主要是传递args的时间,而计算只是总数中的一小部分。所以,我把我的样品放在下面,我很高兴看到有人打磨它。

  3. 你的循环次数太少了。

    循环次数越少,随机性就越大。与我的观点1类似,测试时间仅为0.000x秒。操作系统可能会干扰正在运行的进程。我认为我们应该让测试时间至少持续几秒钟。

  4. C++并不总是比Python快。现在有这么多Python模块/lib可以使用GPU来执行繁重的计算,即使只使用CPU也可以并行地执行矩阵运算。我想,也许您正在评估是否在项目中使用Pybind11。我认为这样的比较毫无价值,因为什么是最好的工具取决于什么是真正的要求,但这是一个很好的教训。我最近遇到一个案例,Python在深度学习中比C++更快。哈哈,有趣吗?

最后,我在PC上运行了我的示例,发现C++的计算速度比Python快100倍。

ComplexApp.cpp:

#include <cmath>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
double Compute( double x, py::array_t<double> ys ) {
//  std::cout << "x:" << std::setprecision( 16 ) << x << std::endl;
auto r = ys.unchecked<1>();
for( py::ssize_t i = 0; i < r.shape( 0 ); ++i ) {
double y = r( i );
//      std::cout << "y:" << std::setprecision( 16 ) << y << std::endl;
x += y;
x *= y;
y = std::max( y, 1.001 );
x /= y;
x *= std::log( y );
}
return x;
};
PYBIND11_MODULE( ComplexCpp, m ) {
m.def( "Compute", &Compute, "a more complicated computing" );
};

tryComplexpp.py

import ComplexCpp
import math
import numpy as np
import random
import time

def PyCompute(x: float, ys: np.ndarray) -> float:
#print(f'x:{x}')
for y in ys:
#print(f'y:{y}')
x += y
x *= y
y = max(y, 1.001)
x /= y
x *= math.log(y)
return x

LOOPS: int = 100000000
if __name__ == "__main__":
# initialize random
x0 = random.random()
""" We store all args in a array, then pass them into both C++ func and
python side, to ensure that args for both sides are same. """
args = np.ndarray(LOOPS, dtype=np.float64)
for i in range(LOOPS):
args[i] = random.random()
print('Args are ready, now start...')
# try it with C++
start_time = time.time()
x = ComplexCpp.Compute(x0, args)
print(f'Computing with C++ in { time.time() - start_time }.n')
# forcely use the result to prevent the entire procedure be optimized(omit)
print(f'The result is {x}n')
# try it with python
start_time = time.time()
x = PyCompute(x0, args)
print(f'Computing with Python in { time.time() - start_time }.n')
# forcely use the result to prevent the entire procedure be optimized(omit)
print(f'The result is {x}n')

最新更新