pybind11:对比C++和Python解线性方程组的速度

news2024/12/29 8:42:40

前言

上篇博客介绍了如何在用pybind11实现ndarray和C++数组的转换自由,pybind11:实现ndarray转C++原生数组(没看过的朋友可以去看一看)下面我们以一个实际的算法例子演示一下如何使用这个技术,方便的实现 Python 调用 C++ 写的算法,并观察两个语言分别运行同一算法时,算法速率的显著性差异

算法选择

解线性方程组在工程乃至很多领域都是一项非常重要的技术,该算法的时间复杂度也是人们一直在追求的。下面就以手写最经典的 Gauss列主元素消去法 来作为实例算法演示C++和Python的运行速率的差距。

它的算法先后对比较简单,主要分为消去回代两个过程,消去即通过矩阵行变换将一个普通的矩阵(增广矩阵)转化为一个上三角矩阵,回代即从主对角线上最后一个元素( x n x_n xn)开始往回代,依次计算出( x n x_n xn, x n − 1 x_{n-1} xn1, … , x 1 x_1 x1)的值,具体过程如下

lll
按照这个思路,直接给出Python和C++的算法代码

C++

#include <iostream>

using namespace std;

// A: 系数矩阵 b: 右侧常数向量 n: 维数  
// 即计算 Ax = b (n个未知数)
double* Solve(double** A, double* d, int n) {

    double** a = new double*[n];
    for (int i = 0; i < n; ++i) {
        a[i] = new double[n+1];
        for (int j = 0; j < n; ++j) {
            a[i][j] = A[i][j];
        }
        a[i][n] = d[i];
    }

    for (int k = 0; k < n - 1; ++k) {
        // 选主元
        int p = k;
        for (int i = k + 1; i < n; ++i) {
            if (std::abs(a[i][k]) > std::abs(a[p][k])) {
                p = i;
            }
        }      
        // 交换行
        double* temp = a[k];
        a[k] = a[p];
        a[p] = temp;

        // 消元
        for (int i = k + 1; i < n; ++i) {
            double factor = a[i][k] / a[k][k];
            for (int j = k; j < n + 1; ++j) {
                a[i][j] -= factor * a[k][j];
            }
        }
    }

    // 回代
    double* x = new double[n];
    x[n - 1] = a[n - 1][n] / a[n - 1][n - 1];
    for (int i = n - 2; i >= 0; --i) {
        double sum = 0;
        for (int j = i + 1; j < n; ++j) {
            sum += a[i][j] * x[j];
        }
        x[i] = (a[i][n] - sum) / a[i][i];
    }

    // 释放动态分配的内存
    for (int i = 0; i < n; ++i) {
        delete[] a[i];
    }
    delete[] a;

    return x;
}

Python

import numpy as np

# A: 系数矩阵 b: 右侧常数向量 
# 即计算 Ax = b
def solve(A, d):
	# 确保以浮点型数据计算
	A.astype(np.float64)
    d.astype(np.float64)
    
    a = np.hstack((A, d.reshape(len(d), 1)))  # 水平拼接(先将d转化为列向量)

    n = len(A[0])
    for k in range(n - 1):
        # 选主元
        for p in range(k+1, n):
            if np.abs(a[p, k]) == np.max(np.abs(a[k:, k])):
                a[k, :], a[p, :] = a[p, :], a[k, :].copy()
        # 消元
        for i in range(k+1, n):
            a[i, k:] = a[i, k:] - a[i, k] / a[k, k] * a[k, k:]
	# 回代
    x = np.zeros(n)
    x[n-1] = a[n-1, n] / a[n-1, n-1]
    for i in range(n-2, -1, -1):
        x[i] = (a[i, n] - np.sum(a[i, i+1:n] * x[i+1:])) / a[i, i]

    return x

不难看出,C++代码远比Python代码复杂,单从使用的循环数量粗糙的分析,C++代码中使用了两个二重循环(水平拼接和选主元)和两个三重循环(消元和回代)而Python代码只用到了两个二重循环(选主元和消元)和一个一重循环(消元和回代),所以无论是算法的时间复杂度还是空间复杂度,Python代码都远小于C++的,那么它们实际的运行速度的快慢呢?

构建项目

构建项目,使用Pybind11将C++代码编译为pyd文件,项目结构即配置过程的详情参考:pybind11:实现Python调用C++代码(入门)

核心cpp代码如下:

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <iostream>
#include "pybind11_tools.cpp"
#include "cpp_code.cpp"

namespace py = pybind11;


py::array_t<double> SolvePybind11(py::array_t<double>& inputMatrix, py::array_t<double>& inputVector) {

    // ndarray转C数组
    NdarrayToCppArray<double>  InputMatrix(inputMatrix); 
    NdarrayToCppArray<double> InputVector(inputVector);

    // 记录长度 (待解方程的维度)
    int len = InputVector.lens[0];

    // 调用C函数
    double* result = Solve(InputMatrix.Matrix, InputVector.Vector, len);

    // 将C数组转化为ndarray
    py::array_t<double> outputArray = CToNdarray(result, len);

    return outputArray;
}

// 绑定C++函数
PYBIND11_MODULE(tryPybind, m) {
    m.def("solve", &SolvePybind11);
}

“pybind11_tools.cpp” 中装有自主编写的实现ndarray和C++数组相互转化的API(InputMatrix, InputVector, CToNdarray),代码详情参考:pybind11:实现ndarray转C++原生数组

调用写好的接口可以很方便的将写好的C++代码打包为二进制文件(pyd文件)提供给Python使用。将生成的pyd文件(tryPybind)放置于测试代码一个目录下,即可开始测试

开始测试

首先测试是否正常(以numpy的(linalg.solve函数))计算的结果为标准答案)

import numpy as np
import python_tool as pytool
import tryPybind  # 编译好的C++二进制文件

# ax = b (随便定义一个方程组)
a = np.array([[1., 5., 3.], 
              [4., 2., 6.],
              [9., 8., 7.]])
b = np.array([10., 20. ,30.])

print('python运算结果:')
x1 = pytool.solve(a, b)
print(x1)

print('C++运算结果:')
x2 = tryPybind.solve(a, b)
print(x2)

print('numpy运算结果:')
x3 = np.linalg.solve(a, b)
print(x3)

结果如下:

yunsaunjieg

算法没大问题,接下来测试对比运行时间,用rand生成一组随机的1000维线性方程组,分别计算运行时间,代码如下:

import numpy as np
import python_tool as pytool
import time
import tryPybind

# ax = b (1000维)
a = np.random.rand(1000, 1000)
b = np.random.rand(1000)

print('计算一个1000维的线性方程组,分别耗时如下:')

t1 = time.time()
x1 = pytool.solve(a, b)
t2 = time.time()
print("Python耗时: " + str(t2 - t1) + '秒')

t1 = time.time()
x2 = tryPybind.solve(a, b)
t2 = time.time()
print("C++耗时:    " + str(t2 - t1) + '秒')

t1 = time.time()
x3 = np.linalg.solve(a, b)
t2 = time.time()
print('numpy耗时:  ' + str(t2 - t1) + '秒')

多次执行该代码,结果如下:

yunsuansj
不难发现,Python代码虽然算法简介,复杂度低,但实际的运算速率远慢于低于C++(接近6倍),以此可直观的看见解释性语言的效率之慢的通病,更能体现出在Python代码中调用写好的C++代码所带来的强大优势。同时也可发现numpy每次都能以不到 0.2 秒的速度算完了一个1000维的线性方程组,其算法之强大。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1316758.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java架构师系统架构高可用维度分析

目录 1 导语2 可用性介绍3 本地高可用-集群、分布式4 本地高可用-数据逻辑保护5 异地容灾-双活、两地三中心6 异地容灾-DRP规划&BCP业务连续性7 多活和妥协方案8 高可用流程9 总结想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导语 Java架构师在进行系统架构设…

蓝桥杯专题-真题版含答案-【排序法 - 改良的选择排序】【插补搜寻法】【稀疏矩阵】【欧拉与鸡蛋】

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

【golang/g3n】3D游戏引擎G3N的windows安装与测试

目录 说在前面安装测试 说在前面 操作系统&#xff1a;win 11go version&#xff1a;go1.21.5 windows/amd64g3n版本&#xff1a;github.com/g3n/engine v0.2.0其他&#xff1a;找了下golang 3d相关的库&#xff0c;目前好像就这个比较活跃 安装 按照官方教程所说&#xff0c;…

ES6 面试题 | 13.精选 ES6 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

IDEA中alt enter不显示创建实现类快捷键

alt enter不显示创建实现类快捷键是因为idea中的设置没打开&#xff0c;按照一下设置打开就可以了。 点击setting-->>editor-->>intentions-->>java下的declaration 如下图所示&#xff1a;

PXI/PCIe/VPX机箱 ARM|x86 + FPGA测试测量板卡解决方案

PXI便携式测控系统是一种基于PXI总线的便携式测试测控系统&#xff0c;它填补了现有台式及机架式仪器在外场测控和便携测控应用上的空白&#xff0c;在军工国防、航空航天、兵器电子、船舶舰载等各个领域的外场测控场合和科学试验研究场合都有广泛的应用。由于PXI便携式测控系统…

HiveSql语法优化一 :分组聚合优化

Hive中未经优化的分组聚合&#xff0c;是通过一个MapReduce Job实现的。Map端负责读取数据&#xff0c;并按照分组字段分区&#xff0c;通过Shuffle&#xff0c;将数据发往Reduce端&#xff0c;各组数据在Reduce端完成最终的聚合运算。 Hive对分组聚合的优化主要围绕着减少Shuf…

Linux 基本语句_15_Tcp并发服务器

原理&#xff1a; 利用父子进程。父进程接收客户端的连接请求&#xff0c;子进程处理客户端的数据处理操作&#xff0c;两者各施其职。最终实现能够多个客户端连接一个服务端的操作。 代码&#xff1a; 服务端代码&#xff1a; #include <stdio.h> #include <sys/…

时序预测 | Python实现LSTM-Attention-XGBoost组合模型电力需求预测

时序预测 | Python实现LSTM-Attention-XGBoost组合模型电力需求预测 目录 时序预测 | Python实现LSTM-Attention-XGBoost组合模型电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从…

Eslint 要被 Oxlint替换了吗

什么是 Oxlint 由于最近的rust在前端领域的崛起,基于rust的前端生态链遭到rust底层重构,最近又爆出OxLint,是一款基于Rust的linter工具。Oxlint在国外前端圈引起热烈讨论,很多大佬给出了高度评价。 事实上,Oxlint 是 Oxc 项目旗下的一款产品,专为 JavaScript 和 TypeSc…

SLAM算法与工程实践——SLAM基本库的安装与使用(5):Ceres优化库

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址&#xff1a; SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

【️接口和抽象类的区别,如何选择?】

✅接口和抽象类的区别&#xff0c;如何选择&#xff1f; ✅ 接口和抽象类的区别✅方法定义✅修饰符✅构造器✅继承和实现✅单继承 、 多实现✅职责不同 ✅什么是模板方法模式&#xff0c;有哪些应用呢&#xff1f;✅典型理解✅示例&#x1f4a1;思考 ✅你在工作中是如何使用设计…

Swin-Transformer 在图像识别中的应用

1. 卷积神经网络简单介绍 图像识别任务主要利用神经网络对图像进行特征提取&#xff0c;最后通过全连接层将特征和分类个数进行映射。传统的网络是利用线性网络对图像进行分类&#xff0c;然而图像信息是二维的&#xff0c;一般来说&#xff0c;图像像素点和周围邻域像素点相关…

【MISRA C 2012】Rule 5.4 宏标识符应该是不同的

1. 规则1.1 原文1.2 分类 2. 关键描述3. Example4. 代码实例 1. 规则 1.1 原文 1.2 分类 规则5.4&#xff1a;宏标识符应该是不同的 Required要求类规范。 2. 关键描述 该规则要求&#xff0c;当定义宏时&#xff0c;其名称与: •当前定义的其他宏的名称;和 •参数的名称。…

网线市场现状与发展趋势预测

随着物联网、5G、云计算等技术的迅速发展&#xff0c;全球对于高速、稳定的网络需求急剧增长&#xff0c;这进一步推动了网线市场的发展。各种网络应用场景&#xff0c;从家庭到企业、数据中心到智能城市&#xff0c;都需要大量的高质量网线来支持数据传输和通信需求。本文将对…

windows 10 安装和配置nginx

1 下载nginx 1.1 下载地址&#xff1a;http://nginx.org/en/download.html 1.2 使用解压到安装目录 1.3 更改配置 conf目录下nginx.conf 修改为未被占用的端口&#xff0c;地址改成你的地址 server {listen 9999;server_name localhost;#charset koi8-r;#access_lo…

超文本传送协议HTTP

目录 HTTP简介&#xff1a; URL的格式&#xff1a; HTTP协议的特点&#xff1a; HTTP/1.0协议&#xff1a; HTTP/1.1协议&#xff1a; HTTP/2: HTTP代理服务器&#xff1a; HTTP的报文结构&#xff1a; 请求报文的特点&#xff1a; 响应报文的特点&#xff1a; Cook…

eNSP小实验---(简单混合)

实验目的&#xff1a;实现vlan10 vlan20 172网段用户互访 1.拓扑图 2.配置 PC1 其它同理 SW4 <Huawei> <Huawei>u t m Info: Current terminal monitor is off. <Huawei>sys <Huawei>sys Enter system view, return user view with CtrlZ. [Hua…

idea第一次提交到git(码云)

1.先创建一个仓库 2.将idea和仓库地址绑定 2.将idea和仓库地址绑定

升华 RabbitMQ:解锁一致性哈希交换机的奥秘【RabbitMQ 十】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 升华 RabbitMQ&#xff1a;解锁一致性哈希交换机的奥秘【RabbitMQ 十】 前言第一&#xff1a;该插件需求为什么需要一种更智能的消息路由方式&#xff1f;一致性哈希的基本概念&#xff1a; 第二&…