概述
- Python 是解释型语言,在进行数学运算场景下,性能是瓶颈。
- C++ 性能卓越,但学习门槛高且开发效率比 Python 低。
- C++ 可以用于密集型计算并用 Python 进行调用。
实现混合编程的方式
将影响性能的核心代码用 C++ 来写,而逻辑开发由 Python 完成。
- 方法一:使用 ctypes 库加载 C++ 编写的动态链接库。
ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型,并允许调用 DLL 或共享库中的函数。可使用该模块以纯 Python 形式对这些库进行封装。
参考链接:https://docs.python.org/zh-cn/3/library/ctypes.html - 方法二:使⽤ pybind11 将 C++ 编译为 Python 库。
参考链接:https://pypi.org/project/pybind11 - 方法三:使⽤ Pythran 库将 Python 直接转换为 C++ 代码。
参考链接:https://pypi.org/project/pythran
ctypes 的使用方法
- 编写 C++ 文件(CalculatePI.cpp):
#include <iostream>
#include <random>
// 比如在C++中调用C库函数,就需要在C++程序中用extern "C"声明要引用的函数。
// 这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。
// extern修饰符可用于指示C或者C++函数的调用规范。
extern "C"{
double get_pi(int total) {
int count = 0;
std::default_random_engine gen;
std::uniform_real_distribution<double> dist(0, 1);
for (int i = 0; i < total; ++i) {
double x = dist(gen);
double y = dist(gen);
double distance = sqrt(x * x + y * y);
if (distance <= 1) {
count += 1;
}
}
double pi = 4.0 * count / total; //此处如果写成4,则计算结果为整数;写成4.0,则计算结果为浮点数。
printf("%d个随机点时,π是:%f", total, pi);
return pi;
}
}
- 将 C++ 文件(CalculatePI.cpp)编译成动态链接库(CalculatePI.so):
g++ -shared -fPIC CalculatePI.cpp -o CalculatePI.so
命令解释:
gcc c语言编译命令
g++ c++语言编译命令
参数 -shared 共享的
参数 -fPIC 告诉编绎器使用 GOT 和 PLT 的方法重定位
参数 -o CalculatePI.so 输出 CalculatePI.so 文件
- 使用 Python 加载动态链接库,调用 C++ 函数(CalculatePI.py):
from ctypes import cdll
if __name__ == '__main__':
# 加载动态链接库
lib = cdll.LoadLibrary('./CalculatePI.so')
# 调用C++函数
print(lib.get_pi(100000000))
注意:打印函数 get_pi 的返回值 pi 有点问题,打印出来的值不对,暂时没找到原因。
pythran 的使用方法
- 编写 Python 文件(CalculatePI.py):
# -*- coding: gbk -*-
import random
def get_pi(total: int) -> float:
"""
蒙特卡罗方法计算PI值:
有一个半径为r=1的圆和边长为1的正方形,圆的面积为 πr^2=π,则正方形内部的相切圆的面积为整个圆的1/4,也就是 π/4,
正方形的面积为1。然后我们向正方形中随机打点,就会有一定的概率落在圆中。
这样我们就可以得到落在圆中的概率就是=圆的面积/正方形面积=π/4,那么就可以推出圆周率的计算公式:
π=4*落在圆中的概率=4*落在圆中的点数/总点数,落在圆中的点可以定义为距离圆点小于1的点。
"""
count = 0
for i in range(total):
x = random.random()
y = random.random()
distance = (x**2 + y**2)**0.5
if distance <= 1:
count += 1
return 4*count/total
- 将 Python 文件(CalculatePI.py)翻译成 C++ 头文件(CalculatePI.hpp):
pythran -e CalculatePI.py -p pythran.optimizations.ConstantFolding -o CalculatePI.hpp
命令解释:
参数 -e 只运行翻译,不编译
参数 -p pythran.optimizations.ConstantFolding 在代码生成之前应用常量合并优化
参数 -o CalculatePI.hpp 输出 CalculatePI.hpp 文件
注意:
- pythran 无法支持所有 python 的功能,只有矩阵计算与数学的数据处理,支持较好。
- 如果 Python 文件包含中文字符,则需要在文件顶部加上
# -*- coding: gbk -*-
,否则翻译会报错。
- 编写 C++ 文件(CalculatePI.cpp)include C++ 头文件(.hpp)并调用里面的函数:
#include "CalculatePI.hpp"
#include <stdio.h>
#include <time.h>
using namespace __pythran_CalculatePI;
int main()
{
time_t start_time = time(NULL);
int total = 100000000;
float pi = get_pi()(total);
printf("%d个随机点时,π是:%f", total, pi);
time_t end_time = time(NULL);
printf("\n程序运行时间:%d秒", end_time - start_time);
return 0;
}
- 将 C++ 文件(CalculatePI.cpp)编译成可执行文件(CalculatePI.exe):
`pythran-config --compiler --cflags` -std=c++11 -fexec-charset=gbk CalculatePI.cpp -o CalculatePI.exe
命令解释:
参数 pythran-config --compiler --cflags 编译需要的库
参数 -std=c++11 使用 c++11 标准编译
参数 -fexec-charset=gbk 使用 gbk 字符集编译
参数 -o CalculatePI.exe 输出 CalculatePI.exe 文件
注意:
- Windows 需要安装 MinGW。
- Windows 需要先执行命令
pythran-config --compiler --cflags
查看编译需要的库,然后再执行命令g++ -DENABLE_PYTHON_MODULE -D__PYTHRAN__=3 -DPYTHRAN_BLAS_NONE -Ie:\jetbrains\pycharmprojects\myvenv\venv20240503\lib\site-packages/pythran -Ie:\jetbrains\pycharmprojects\myvenv\venv20240503\lib\site-packages\numpy\core\include -Ie:\jetbrains\pycharmprojects\myvenv\venv20240503\lib\site-packages\numpy\core\include -IE:\Python\include -std=c++11 -fexec-charset=gbk CalculatePI.cpp -o CalculatePI.exe
进行编译。
- 运行可执行文件(CalculatePI.exe):