1、Python 怎么调整 C/C++ 2、在计算密集型的应用场景下两者的性能差异有多少。
背景
在阅读 websockets 发现这样一段
4.Performance: memory usage is optimized and configurable. A C extension accelerates expensive operations. It's pre-compiled for Linux, macOS and Windows and packaged in the wheel format for each system and Python version
意思是说对于开销比较大的计算任务,在 websockets 内部使用一个 C 语言写的扩展来完成它的计算,作者希望以这种方式来提升程序的性能。那么问题就来了,C 语言对比 Python 语言在处理同一个问题的时候会快多少呢?
设计测试场景
理论上来讲为了尽可能的准确且全面,我应该针对不同的场景都设置有测试用例。这样一来就不知道什么时候才能搞完,就留到下一期吧。
所以这次我就想来一个简单的,一来可以知道计算密集型应用场景下大致差多少倍,二来详细的介绍一下怎么用 Python 调用 C/C++ 。
最终我把场景设定为计算 婓波那契数列的第 n 位 。
Python 实现
下面看用 Python 实现计算婓波那契数列第 n 位,并测量它的耗时。
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import time
def fib(n):
"""
求婓波那契数列的第 n 位的值
Parameter
---------
n: int
婓波那契数列的第 n 位
"""
if n <= 1:
return 1
else:
return fib(n -1) + fib(n - 2)
if __name__ == "__main__":
# 计算婓波那契数列的第 39 位,并打印耗时
start_at = time.time()
fib(39)
end_at = time.time()
print(f"total-time = {end_at - start_at}")
在我的 Mac 上执行耗时 18.38 s:
python3 fib.py
total-time = 18.83948802947998
C++ 实现
从执行耗时上看 Python 用递归算法计算第 39 位的效果并不理想,我们看一下用 C++ 实现执行时间又是多少。
#include<iostream>
#include<ctime>
using namespace std;
int fib(int n)
{
if(n <= 1)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
int main()
{
clock_t start_at = clock();
int number = fib(39);
clock_t end_at = clock();
cout<<"total-time = "<<double(end_at - start_at)/CLOCKS_PER_SEC<<endl;
return 0;
}
在我的 Mac 上执行耗时 0.34 s:
g++ -o fib-cpp main.cpp && ./fib-cpp
total-time = 0.345918
Python vs C++
针对计算婓波那契数列的场景,两种不同语言的耗时如下。
测试项目 | 语言 | 耗时(s) |
---|---|---|
计算婓波那契数列 | C++ | 0.34 |
计算婓波那契数列 | Python | 18.38 |
两者的性能相关 54 倍 ( 54 = 18.38/0.34 ) 。
Python 调用 C++ 的实现思路
Python 解释器不能直接调用 C++ 语言的源文件,但是只要我们把 C++ 的源文件编译成共享库(linux 平台的 so 文件,windows 平台的 dll 文件)之后,Python 就可以把他们当作一个模块来使用了。
在实际的代码编写中我们的库还要是按 Python 已经定义好的规范编写才行,不然解释器还是识别不了。
第一步 实现功能
int fib(int n)
{
if(n <= 1)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
第二步 用 Python 的数据类型包装一下我们的函数
static PyObject *fib_wraper(PyObject *self,PyObject *args)
{
int n = 0,result = 0;
PyArg_ParseTuple(args,"i",&n);
result = fib(n);
return Py_BuildValue("i",result);
}
第三步 定义模块的函数列表
static PyMethodDef methods[] = {
{"fib",fib_wraper,METH_VARARGS,"fib generator ."},
{0,0,0,0}
};
第四步 定义模块
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"fibcpp",
"a simple module",
-1,
methods
};
第五步 定义模块的初始化逻辑
PyMODINIT_FUNC PyInit_fibcpp(void)
{
return PyModule_Create(&module);
}
完整的 C++ 代码实现(源文件名 main.cpp)
#include<iostream>
#include<ctime>
#include <Python.h>
using namespace std;
int fib(int n)
{
if(n <= 1)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
}
static PyObject *fib_wraper(PyObject *self,PyObject *args)
{
int n = 0,result = 0;
PyArg_ParseTuple(args,"i",&n);
result = fib(n);
return Py_BuildValue("i",result);
}
static PyMethodDef methods[] = {
{"fib",fib_wraper,METH_VARARGS,"fib generator ."},
{0,0,0,0}
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"fibcpp",
"a simple module",
-1,
methods
};
PyMODINIT_FUNC PyInit_fibcpp(void)
{
return PyModule_Create(&module);
}
编译 C++ 代码成库文件
我在 linux 平台做的开发,windows 上应该也差不多,编译命令如下。
g++ -fPIC -I /usr/local/python/include/python3.8/ -o fibcpp.so -shared main.cpp
测试 Python 调用 C++ 的效率
#!/usr/bin/env python3
# -*- coding: utf8 -*-
import time
from fibcpp import fib
if __name__ == "__main__":
# 计算婓波那契数列的第 39 位,并打印耗时
start_at = time.time()
fib(39)
end_at = time.time()
print(f"total-time = {end_at - start_at}")
运行的效果如下。
python3 fib-embbed.py
total-time = 0.6759123802185059
总结
对于同一问题采用同一算法、不同实现方式的效率如下表。
测试项目 | 语言 | 耗时(s) |
---|---|---|
计算婓波那契数列 | C++ | 0.34 |
计算婓波那契数列 | Python-Call-C++ | 0.67 |
计算婓波那契数列 | Python | 18.38 |