绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
将 Python 运行速度提升 30 倍的神技巧
2021-08-23 09:32:17

当 Python 面临运算密集型任务时,其速度总是显得力不从心。要提升 Python 代码运行速度有多种方法,如 c*、cython、CFFI 等,本篇文章主要从 c* 方面介绍如何提升 Python 的运行速度😗。
c* 是 Python 的内置库,利用 c* 可以调用 C/C++ 编译成的 so 或 dll 文件 (so 存在 linux/MacOS 中,dll 存在于 windows)😏,简单而言,就是将计算压力较大的逻辑利用 C/C++ 来实现,然后编译成 so 或 dll 文件,再利用 c* 加载进 Python,从而将计算压力大、耗时较长的逻辑交于 C/C++ 去执行。如 Numpy、Pandas 这些库其底层其实都是 C/C++ 来实现的🤩。
下面代码的运行环境为:MacOS 、 Python3.7.3

纯 Python 实现

为了对比出使用 c* 后程序运行速度的变化,先使用纯 Python 代码实现一段逻辑,然后再利用 C 语言去实现相同的逻辑😉。
这里为了模仿运算密集任务,实现一段逻辑用于计算一个集合中点与点之间的距离以及实现一个操作字符串的逻辑,具体代码如下:
  1. import random

  2. import time


  3. # 点

  4. class Point():

  5. def __init__(self, x, y):

  6. self.x = x

  7. self.y = y


  8. class Test():

  9. def __init__(self, string, nb):

  10. self.string = string

  11. self.points = []

  12. # 初始化点集合

  13. for i in range(nb):

  14. self.points.append(Point(random.random(), random.random()))

  15. self.distances = []


  16. # 增量字符串

  17. def increment_string(self, n):

  18. tmp = ""

  19. # 每个字符做一次偏移

  20. for c in self.string:

  21. tmp += chr(ord(c) + n)

  22. self.string = tmp


  23. # 这个函数计算列表中每个点之间的距离

  24. def distance_between_points(self):

  25. for i, a in enumerate(self.points):

  26. for b in self.points:

  27. # 距离公式

  28. self.distances.append(((b.x - a.x) ** 2 + (b.y - b.x) ** 2) ** 0.5)


  29. if __name__ == '__main__':

  30. start_time = time.time()

  31. test = Test("A nice sentence to test.", 10000)

  32. test.increment_string(-5) # 偏移字符串中的每个字符

  33. test.distance_between_points() # 计算集合中点与点之间的距离

  34. print('pure python run time:%s'%str(time.time()-start_time))

上述代码中,定义了 Point 类型,其中有两个属性,分别是 x 与 y,用于表示点在坐标系中的位置😐,然后定义了 Test 类,其中的 increment_string () 方法用于操作字符串,主要逻辑就是循环处理字符串中的每个字符,首先通过 ord () 方法将字符转为 unicode 数值,然后加上对应的偏移 n,接着在通过 chr () 方法将数值转换会对应的字符😬。
此外还实现了 distance_between_points () 方法,该方法的主要逻辑就是利用双层 for 循环,计算集合中每个点与其他点的距离。使用时,创建了 10000 个点进行程序运行时长的测试🤭。
多次执行这份代码,其运行时间大约在 39.4 左右🤔
  1. python 1.py

  2. pure python run time:39.431304931640625

使用 c* 提速度代码

要使用 c*,首先就要将耗时部分的逻辑通过 C 语言实现,并将其编译成 so 或 dll 文件,因为我使用的是 MacOS,所以这里会将其编译成 so 文件😐,先来看一下上述逻辑通过 C 语言实现的具体代码,如下:
  1. #include <stdlib.h>

  2. #include <math.h>


  3. // 点结构

  4. typedef struct s_point

  5. {

  6. double x;

  7. double y;

  8. } t_point;


  9. typedef struct s_test

  10. {

  11. char *sentence; // 句子

  12. int nb_points;

  13. t_point *points; // 点

  14. double *distances; // 两点距离,指针

  15. } t_test;


  16. // 增量字符串

  17. char *increment_string(char *str, int n)

  18. {

  19. for (int i = ; str[i]; i++)

  20. // 每个字符做一次偏移

  21. str[i] = str[i] + n;

  22. return (str);

  23. }


  24. // 随机生成点集合

  25. void generate_points(t_test *test, int nb)

  26. {

  27. // calloc () 函数用来动态地分配内存空间并初始化为 0

  28. // 其实就是初始化变量,为其分配内存空间

  29. t_point *points = calloc(nb + 1, sizeof(t_point));


  30. for (int i = ; i < nb; i++)

  31. {

  32. points[i].x = rand();

  33. points[i].y = rand();

  34. }

  35. // 将结构地址赋值给指针

  36. test->points = points;

  37. test->nb_points = nb;

  38. }


  39. // 计算集合中点的距离

  40. void distance_between_points(t_test *test)

  41. {

  42. int nb = test->nb_points;

  43. // 创建变量空间

  44. double *distances = calloc(nb * nb + 1, sizeof(double));


  45. for (int i = ; i < nb; i++)

  46. for (int j = ; j < nb; j++)

  47. // sqrt 计算平方根

  48. distances[i * nb + j] = sqrt((test->points[j].x - test->points[i].x) * (test->points[j].x - test->points[i].x) + (test->points[j].y - test->points[i].y) * (test->points[j].y - test->points[i].y));

  49. test->distances = distances;

  50. }

其中具体的逻辑不再解释,可以看注释理解其中的细节,通过 C 语言实现后,接着就可以通过 gcc 来编译 C 语言源文件,将其编译成 so 文件🤗,命令如下:
  1. // 生成 .o 文件

  2. gcc -c fastc.c

  3. // 利用 .o 文件生成so文件

  4. gcc -shared -fPIC -o fastc.so fastc.o

获得了 fastc.so 文件后,接着就可以利用 c* 将其调用并直接使用其中的方法了,需要注意的是「Windows 系统体系与 Linux/MacOS 不同,c* 使用方式会有差异」😳,至于 c* 的具体用法,后面会通过单独的文章进行讨论。
c* 使用 fastc.so 的代码如下:
  1. import c*

  2. from c* import *

  3. from c*.util import find_library

  4. import time



  5. # 定义结构,继承自c*.Structure,与C语言中定义的结构对应

  6. class Point(c*.Structure):

  7. _fields_ = [('x', c*.c_double), ('y', c*.c_double)]


  8. class Test(c*.Structure):

  9. _fields_ = [

  10. ('sentence', c*.c_char_p),

  11. ('nb_points', c*.c_int),

  12. ('points', c*.POINTER(Point)),

  13. ('distances', c*.POINTER(c_double)),

  14. ]


  15. # Lib C functions

  16. _libc = c*.CDLL(find_library('c'))

  17. _libc.free.arg* = [c*.c_void_p]

  18. _libc.free.restype = c*.c_void_p


  19. # Lib shared functions

  20. _libblog = c*.CDLL("./fastc.so")

  21. _libblog.increment_string.arg* = [c*.c_char_p, c*.c_int]

  22. _libblog.increment_string.restype = c*.c_char_p

  23. _libblog.generate_points.arg* = [c*.POINTER(Test), c*.c_int]

  24. _libblog.distance_between_points.arg* = [c*.POINTER(Test)]


  25. if __name__ == '__main__':


  26. start_time = time.time()


  27. # 创建

  28. test = {}

  29. test['sentence'] = "A nice sentence to test.".encode('utf-8')

  30. test['nb_points'] =

  31. test['points'] = None

  32. test['distances'] = None

  33. c_test = Test(**test)

  34. ptr_test = c*.pointer(c_test)


  35. # 调用so文件中的c语言方法

  36. _libblog.generate_points(ptr_test, 10000)

  37. ptr_test.contents.sentence = _libblog.increment_string(ptr_test.contents.sentence, -5)

  38. _libblog.distance_between_points(ptr_test)

  39. _libc.free(ptr_test.contents.points)

  40. _libc.free(ptr_test.contents.distances)


  41. print('c* run time: %s'%str(time.time() - start_time))

多次执行这份代码,其运行时间大约在 1.2 左右😯
  1. python 2.py

  2. c* run time: 1.2614238262176514

相比于纯 Python 实现的代码快了 30 倍有余😲

结尾

本节简单的讨论了如何利用 c* 与 C/C++ 来提升 Python 运行速度,有人可能会提及使用 asyncio 异步的方式来提升 Python 运行速度,但这种方式只能提高 Python 在 IO 密集型任务中的运行速度,对于运算密集型的任务效果并不理想。

以下文章来源于懒编程 ,作者ayuliao


技术交流微信群,技术探讨 信息分享 学习互助,还有直播等福利活动等着你~

分享好友

分享这个小栈给你的朋友们,一起进步吧。

人生苦短,不如学Python
创建时间:2020-06-18 16:48:21
Python是一种跨平台的计算机程序设计语言。 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • liuxuhui
    专家
戳我,来吐槽~