使用 ctypes 将 Python 运行速度提升 30 倍
簡(jiǎn)介
當(dāng) Python 面臨運(yùn)算密集型任務(wù)時(shí),其速度總是顯得力不從心。要提升 Python 代碼運(yùn)行速度有多種方法,如 ctypes、cython、CFFI 等,本篇文章主要從 ctypes 方面介紹如何提升 Python 的運(yùn)行速度。
ctypes 是 Python 的內(nèi)置庫(kù),利用 ctypes 可以調(diào)用 C/C++ 編譯成的 so 或 dll 文件 (so 存在 linux/MacOS 中,dll 存在于 windows),簡(jiǎn)單而言,就是將計(jì)算壓力較大的邏輯利用 C/C++ 來(lái)實(shí)現(xiàn),然后編譯成 so 或 dll 文件,再利用 ctypes 加載進(jìn) Python,從而將計(jì)算壓力大、耗時(shí)較長(zhǎng)的邏輯交于 C/C++ 去執(zhí)行。如 Numpy、Pandas 這些庫(kù)其底層其實(shí)都是 C/C++ 來(lái)實(shí)現(xiàn)的。
下面代碼的運(yùn)行環(huán)境為:MacOS 、 Python3.7.3
純 Python 實(shí)現(xiàn)
為了對(duì)比出使用 ctypes 后程序運(yùn)行速度的變化,先使用純 Python 代碼實(shí)現(xiàn)一段邏輯,然后再利用 C 語(yǔ)言去實(shí)現(xiàn)相同的邏輯。
這里為了模仿運(yùn)算密集任務(wù),實(shí)現(xiàn)一段邏輯用于計(jì)算一個(gè)集合中點(diǎn)與點(diǎn)之間的距離以及實(shí)現(xiàn)一個(gè)操作字符串的邏輯,具體代碼如下:
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書(shū)! ''' import random import time# 點(diǎn) class Point():def __init__(self, x, y):self.x = xself.y = yclass Test():def __init__(self, string, nb):self.string = stringself.points = []# 初始化點(diǎn)集合for i in range(nb):self.points.append(Point(random.random(), random.random()))self.distances = []# 增量字符串def increment_string(self, n):tmp = ""# 每個(gè)字符做一次偏移for c in self.string:tmp += chr(ord(c) + n)self.string = tmp# 這個(gè)函數(shù)計(jì)算列表中每個(gè)點(diǎn)之間的距離def distance_between_points(self):for i, a in enumerate(self.points):for b in self.points:# 距離公式self.distances.append(((b.x - a.x) ** 2 + (b.y - b.x) ** 2) ** 0.5)if __name__ == '__main__':start_time = time.time()test = Test("A nice sentence to test.", 10000)test.increment_string(-5) # 偏移字符串中的每個(gè)字符test.distance_between_points() # 計(jì)算集合中點(diǎn)與點(diǎn)之間的距離print('pure python run time:%s'%str(time.time()-start_time))上述代碼中,定義了 Point 類型,其中有兩個(gè)屬性,分別是 x 與 y,用于表示點(diǎn)在坐標(biāo)系中的位置,然后定義了 Test 類,其中的 increment_string () 方法用于操作字符串,主要邏輯就是循環(huán)處理字符串中的每個(gè)字符,首先通過(guò) ord () 方法將字符轉(zhuǎn)為 unicode 數(shù)值,然后加上對(duì)應(yīng)的偏移 n,接著在通過(guò) chr () 方法將數(shù)值轉(zhuǎn)換會(huì)對(duì)應(yīng)的字符。
此外還實(shí)現(xiàn)了 distance_between_points () 方法,該方法的主要邏輯就是利用雙層 for 循環(huán),計(jì)算集合中每個(gè)點(diǎn)與其他點(diǎn)的距離。使用時(shí),創(chuàng)建了 10000 個(gè)點(diǎn)進(jìn)行程序運(yùn)行時(shí)長(zhǎng)的測(cè)試。
多次執(zhí)行這份代碼,其運(yùn)行時(shí)間大約在 39.4 左右
python 1.py pure python run time:39.431304931640625使用 ctypes 提速度代碼
要使用 ctypes,首先就要將耗時(shí)部分的邏輯通過(guò) C 語(yǔ)言實(shí)現(xiàn),并將其編譯成 so 或 dll 文件,因?yàn)槲沂褂玫氖?MacOS,所以這里會(huì)將其編譯成 so 文件,先來(lái)看一下上述邏輯通過(guò) C 語(yǔ)言實(shí)現(xiàn)的具體代碼,如下:
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書(shū)! ''' #include <stdlib.h> #include <math.h>#點(diǎn)結(jié)構(gòu) typedef struct s_point {double x;double y; } t_point;typedef struct s_test {char *sentence; // 句子int nb_points; t_point *points; // 點(diǎn)double *distances; // 兩點(diǎn)距離,指針 } t_test;#增量字符串 char *increment_string(char *str, int n) {for (int i = 0; str[i]; i++)// 每個(gè)字符做一次偏移str[i] = str[i] + n;return (str); }#隨機(jī)生成點(diǎn)集合 void generate_points(t_test *test, int nb) {#calloc () 函數(shù)用來(lái)動(dòng)態(tài)地分配內(nèi)存空間并初始化為 0#其實(shí)就是初始化變量,為其分配內(nèi)存空間t_point *points = calloc(nb + 1, sizeof(t_point));for (int i = 0; i < nb; i++){points[i].x = rand();points[i].y = rand();}# 將結(jié)構(gòu)地址賦值給指針test->points = points;test->nb_points = nb; }#計(jì)算集合中點(diǎn)的距離 void distance_between_points(t_test *test) {int nb = test->nb_points; # 創(chuàng)建變量空間double *distances = calloc(nb * nb + 1, sizeof(double));for (int i = 0; i < nb; i++)for (int j = 0; j < nb; j++)#sqrt 計(jì)算平方根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));test->distances = distances; }其中具體的邏輯不再解釋,可以看注釋理解其中的細(xì)節(jié),通過(guò) C 語(yǔ)言實(shí)現(xiàn)后,接著就可以通過(guò) gcc 來(lái)編譯 C 語(yǔ)言源文件,將其編譯成 so 文件,命令如下:
#生成 .o 文件 gcc -c fastc.c #利用 .o 文件生成so文件 gcc -shared -fPIC -o fastc.so fastc.o獲得了 fastc.so 文件后,接著就可以利用 ctypes 將其調(diào)用并直接使用其中的方法了,需要注意的是「Windows 系統(tǒng)體系與 Linux/MacOS 不同,ctypes 使用方式會(huì)有差異」,至于 ctypes 的具體用法,后面會(huì)通過(guò)單獨(dú)的文章進(jìn)行討論。
ctypes 使用 fastc.so 的代碼如下:
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴, 互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書(shū)! ''' import ctypes from ctypes import * from ctypes.util import find_library import time# 定義結(jié)構(gòu),繼承自ctypes.Structure,與C語(yǔ)言中定義的結(jié)構(gòu)對(duì)應(yīng) class Point(ctypes.Structure):_fields_ = [('x', ctypes.c_double), ('y', ctypes.c_double)]class Test(ctypes.Structure):_fields_ = [('sentence', ctypes.c_char_p),('nb_points', ctypes.c_int),('points', ctypes.POINTER(Point)),('distances', ctypes.POINTER(c_double)),]# Lib C functions _libc = ctypes.CDLL(find_library('c')) _libc.free.argtypes = [ctypes.c_void_p] _libc.free.restype = ctypes.c_void_p# Lib shared functions _libblog = ctypes.CDLL("./fastc.so") _libblog.increment_string.argtypes = [ctypes.c_char_p, ctypes.c_int] _libblog.increment_string.restype = ctypes.c_char_p _libblog.generate_points.argtypes = [ctypes.POINTER(Test), ctypes.c_int] _libblog.distance_between_points.argtypes = [ctypes.POINTER(Test)]if __name__ == '__main__':start_time = time.time()# 創(chuàng)建test = {}test['sentence'] = "A nice sentence to test.".encode('utf-8')test['nb_points'] = 0test['points'] = Nonetest['distances'] = Nonec_test = Test(**test)ptr_test = ctypes.pointer(c_test)# 調(diào)用so文件中的c語(yǔ)言方法_libblog.generate_points(ptr_test, 10000)ptr_test.contents.sentence = _libblog.increment_string(ptr_test.contents.sentence, -5)_libblog.distance_between_points(ptr_test)_libc.free(ptr_test.contents.points)_libc.free(ptr_test.contents.distances)print('ctypes run time: %s'%str(time.time() - start_time))多次執(zhí)行這份代碼,其運(yùn)行時(shí)間大約在 1.2 左右
python 2.py ctypes run time: 1.2614238262176514相比于純 Python 實(shí)現(xiàn)的代碼快了 30 倍有余
結(jié)尾
本節(jié)簡(jiǎn)單的討論了如何利用 ctypes 與 C/C++ 來(lái)提升 Python 運(yùn)行速度,有人可能會(huì)提及使用 asyncio 異步的方式來(lái)提升 Python 運(yùn)行速度,但這種方式只能提高 Python 在 IO 密集型任務(wù)中的運(yùn)行速度,對(duì)于運(yùn)算密集型的任務(wù)效果并不理想,最后歡迎學(xué)習(xí) HackPython 的教學(xué)課程并感覺(jué)您的閱讀與支持。
總結(jié)
以上是生活随笔為你收集整理的使用 ctypes 将 Python 运行速度提升 30 倍的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2019 Python100道 面试 题
- 下一篇: Python中有几种办法交换两个变量的值