python调用C++混合编程:VScode配置pybind11环境(windows)

python在密集计算任务上如何有很多循环,有没有相应的三方库效率将大大降低。pybind11可以让python与C++混合编程,将复杂的算法部分封装到C++库进行计算,python负责流程的串通。这样安排即避开了性能瓶颈,又实现了快速开发,是一个非常好的解决方案。但是在windows上,pybind11推荐在Visual Studio编译pyd,Visual Studio体积太大,动则10个G以上。如果只就为了给python写三方模块非常不划算,经过探索采用vscode+cmake+llvm-mingw的方式体积会小很多。

1.首先下载vscode,安装Chinese,Code runner,C/C++,Cmake,CMake Tools插件

2.再下载

cmake:Download CMake

llvm-mingw:Releases · mstorsjo/llvm-mingw (github.com)

我下载的是

llvm-mingw-20240619-msvcrt-x86_64.zip

解压后添加至环境变量

3.下载pybind11

GitHub - pybind/pybind11: Seamless operability between C++11 and Python

直接clone到本地

4.在同级目录建立CMakeLists.txt文件,写入以下内容:

cmake_minimum_required(VERSION 3.23)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# 设置编译器编译模式,对于编译用的Debug模式和调试用的Release模式
# 在Debug模式中,程序运行较慢,当可以在IDE中进行断点调试
# 在Release模式中,运行速度较快,但没有调试信息。不设置默认是Debug模式
set( CMAKE_BUILD_TYPE "Debug")
# set( CMAKE_BUILD_TYPE "Release")

# 防止报错找不到DLL,用-static编译pyd减少依赖
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") 
# 设置python解释器路径,以便编译不同python版本的pyd库
set(PYTHON_EXECUTABLE "D:/Desktop/111/Python39/python.exe")
# 设置Cmake工程名字
project(HelloWorld)
# 添加依赖库路径
add_subdirectory(pybind11)
# 指定编译成pyd模块的cpp文件与模块名字
pybind11_add_module(HelloWorld hello.cpp)

5.再建立hello.cpp写一个简单的hello world程序,并用pybind11导出函数

#include <pybind11/pybind11.h>
int add(int i,int j)
{
    return i + j;
}

PYBIND11_MODULE(HelloWorld,m)
{
    m.doc() = "pybind11 hello world example";//module docstring

    m.def("add",&add,"A function which adds two numbers");
}

6.配置Cmake的C++解释器

按control+shift+P调出命令行,输入cmake:configure,选择配置的llvm-mingw 编译器

或者点击左边的cmake-配置-编辑,再选择对应的编译器

最后用配置好的Cmake与C++编译器编译pyd库,点击下面的生成(build)。会在同级目录生成一个build目录,下面有HelloWorld.cp39-win_amd64.pyd

此时编写一个python文件导入pyd模块测试,可正确导入模块与调用add函数。此后就可以愉快的进行python/C++混合编程啦,祝你玩得愉快~

1亿次累加测试 与 图片numpy数组转换成灰度图测试


C++代码

#include<iostream>
#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>
using namespace pybind11;

// 1.num次循环累加测试函数
long long int sum(int num){
    long long int sum = 0;
    for(int i=0;i<num;i++){
        sum += i;
    }
    return sum;
}

// 2.图片numpy数组转换成gray灰度图测试函数
array_t<double> rgb_to_gray(array_t<double> & img_rgb)
{
	if(img_rgb.ndim()!=3)
	{
		throw std::runtime_error("RGB image must has 3 channels!");
	}
	array_t<double> img_gray=array_t<double>(img_rgb.shape()[0]*img_rgb.shape()[1]);
	img_gray.resize({img_rgb.shape()[0],img_rgb.shape()[1]});
	auto rgb=img_rgb.unchecked<3>();
	auto gray=img_gray.mutable_unchecked<2>();
	for(int i=0;i<img_rgb.shape()[0];i++)
	{
		for(int j=0;j<img_rgb.shape()[1];j++)
		{
			auto R=rgb(i,j,0);
			auto G=rgb(i,j,1);
			auto B=rgb(i,j,2);
			auto GRAY=(R*30+G*59+B*11+50)/100;
			gray(i,j)=(GRAY);
		}
	}
	return img_gray;
}

PYBIND11_MODULE(example,m)
{
	m.doc()="simple demo";
	m.def("rgb_to_gray",&rgb_to_gray);
	m.def("sum",&sum);
}

python测试代码

import cv2
import build.example as example
import time
import numpy as np

# 1.num次循环累加测试函数
def sum(num):
	sum = 0
	for i in range(num):
		sum += i
	return sum

# 2.图片numpy数组转换成gray灰度图测试函数
def rgb_to_gray(img_rgb):
	if img_rgb.shape[2]!=3:
		print('image channels is 3')
	h,w,c=img_rgb.shape
	gray=np.zeros(shape=(h,w),dtype=np.uint8)
	for i in range(h):
		for j in range(w):
			R=img_rgb[i,j,0]
			G=img_rgb[i,j,1]
			B=img_rgb[i,j,2]
			GRAY=(R*30+G*59+B*11+50)/100
			gray[i,j]=np.uint8(GRAY)
	return gray

if __name__ == '__main__':
	img_rgb=cv2.imread("./src/D65.jpg")
	num = 100000000
	# print(f"测试sum循环{num}次")
	print(f"TEST sum loop {num} times")

	t1=time.time()
	re = example.sum(num)
	t2=time.time()
	print(f"pybind11.sum={re} time:{t2-t1}/s")

	t1=time.time()
	re = sum(num)
	t2=time.time()
	print(f"python.sum={re} time:{t2-t1}/s \n")

	# print(f"图片numpy数组转换成gray灰度图")
	print(f"TEST numpy array to gray image")

	t1=time.time()
	cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
	t2=time.time()
	print(f"cv2.cvtColor time:{t2-t1}/s")

	t1=time.time()
	example.rgb_to_gray(img_rgb)
	t2=time.time()
	print(f"pybind11.rgb_to_gray time:{t2-t1}/s")

	t1=time.time()
	rgb_to_gray(img_rgb)
	t2=time.time()
	print(f"python.rgb_to_gray time:{t2-t1}/s")

DUG模式测试结果,

sum累加函数:C++是python的21倍

rgb_to_gray函数:C++是python的96倍,是opencv的1/170倍

Release模式测试结果,

sum累加函数:C++是python的360倍

rgb_to_gray函数:C++是python的985倍,是opencv的1/14倍

Release速度是Debug模式的10~13倍,所以正式打包一定要用Release模式.

更重要的是C++可以开多线程碾压python的GIL锁,进一步拉开速度差异

注意:

用什么python版本编译的就得在对应版本应用pyd,否则也会报错无法找到该模块。安装的python版本最好是正式版本编译的pyd通用性更好,不要用alpha\beta等版本容易报错。不要用embeddable版本,缺少include/libs等库会报错。用正式版本的python编译好后,可以给相同或相近的alpha/beta/embeddable的python版本用。

用MinGW64的GCC编译的pyd文件无法在win上使用,有时会报错无法找到该模块。换成vs2022自带的MSC编译器就没这个问题了。有的解释是Win上的python解释器是用MSC编译的,然后它使用的二进制模块也需要是MSC编译的。Visual Studio的MSC太大,可以换成 llvm-mingw:Releases · mstorsjo/llvm-mingw (github.com)(实验发现mingw64编译的pyd库也可以被正确导入)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值