前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 性能之颠

Python 性能之颠

作者头像
初代庄主
发布2021-12-06 10:19:07
4350
发布2021-12-06 10:19:07
举报
文章被收录于专栏:初代庄主
核心价值

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 位,并测量它的耗时。

代码语言:javascript
复制
#!/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:

代码语言:javascript
复制
python3 fib.py
total-time = 18.83948802947998

C++ 实现

从执行耗时上看 Python 用递归算法计算第 39 位的效果并不理想,我们看一下用 C++ 实现执行时间又是多少。

代码语言:javascript
复制
#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:

代码语言:javascript
复制
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 已经定义好的规范编写才行,不然解释器还是识别不了。

第一步 实现功能

代码语言:javascript
复制
int fib(int n)
{
    if(n <= 1)
    {
        return 1;
    }
    else 
    {
        return fib(n - 1) + fib(n - 2);
    }
}

第二步 用 Python 的数据类型包装一下我们的函数

代码语言:javascript
复制
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);
}

第三步 定义模块的函数列表

代码语言:javascript
复制
static PyMethodDef methods[] = {
    {"fib",fib_wraper,METH_VARARGS,"fib generator ."},
    {0,0,0,0}
};

第四步 定义模块

代码语言:javascript
复制
static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT,
    "fibcpp",
    "a simple module",
    -1,
    methods
};

第五步 定义模块的初始化逻辑

代码语言:javascript
复制
PyMODINIT_FUNC PyInit_fibcpp(void)
{
    return PyModule_Create(&module);
}

完整的 C++ 代码实现(源文件名 main.cpp)

代码语言:javascript
复制
#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 上应该也差不多,编译命令如下。

代码语言:javascript
复制
g++ -fPIC -I /usr/local/python/include/python3.8/ -o fibcpp.so -shared main.cpp

测试 Python 调用 C++ 的效率

代码语言:javascript
复制
#!/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}")

运行的效果如下。

代码语言:javascript
复制
python3 fib-embbed.py 
total-time = 0.6759123802185059

总结

对于同一问题采用同一算法、不同实现方式的效率如下表。

测试项目

语言

耗时(s)

计算婓波那契数列

C++

0.34

计算婓波那契数列

Python-Call-C++

0.67

计算婓波那契数列

Python

18.38

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 初代庄主 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档