Python?提速器numba
Python
python 真的太好用了,但是它真的好慢?。匏? ; C++ 很快,但是真的好難寫啊,此生能不碰它就不碰它。老天啊,有沒有什么兩全其美的辦法呢?俗話說的好:辦法總是比困難多,大家都有這個問題,自然也就有大佬來試著解決這個問題,這就請出我們今天的主角: numba
不過在介紹 numba 之前,我們還是得來看看 python 為什么這么慢:
1.為什么 python 這么慢
用過 python 的人都知道, 尤其是在有循環(huán)的情況下,python 會比 C++ 慢很多,所以很多人都避免在 python 代碼里引入復(fù)雜的 for 循環(huán)。我們可以想想 python
和 C++ 寫起來有哪些區(qū)別呢:
動態(tài)變量
如果你寫過 C/C++ 就會發(fā)現(xiàn),我們需要對變量類型有嚴格的定義,我們需要定義變量的類型是 int 或者 float 之類的。但是 python
就不一樣了,寫過的 python 的人都知道,它去掉了變量申明和數(shù)據(jù)類型。也就是說,無論啥數(shù)據(jù),咱啥都不用管,想存就存!那么 python 是如何做到這樣灑脫自由的呢?這就不得不提 python 中萬物皆是對象了,真正的數(shù)據(jù)是存在對象里面的。對于一個簡單的兩個變量的加法,python 每次在做運算的時候都得先判斷變量的類型,再取出來進行運算,而對于 C 來說,簡單的內(nèi)存讀寫和機器指令 ADD 即可。其實在 C/C++ 中也有可變數(shù)據(jù)類型,但是其聲明是非常復(fù)雜的,是一種非常令人頭疼的結(jié)構(gòu)。
解釋性語言
C/C++ 這類編譯性語言最大的好處就是其編譯過程是發(fā)生在運行之前的,源代碼在調(diào)用前被編譯器轉(zhuǎn)換為可執(zhí)行機器碼,這樣就節(jié)約了大量的時間。而 python
作為一種解釋性語言,沒法做到一次編譯,后續(xù)可以直接運行,每次運行的時候都要重新將源代碼通過解釋器轉(zhuǎn)化為機器碼。這樣一個好處就是非常容易 debug
( 這里要再次感嘆一下 python 真不愧是新手友好型語言~), 當然,這個問題自然也是有嘗試解決的辦法,一個很重要的技術(shù)就是 JIT (Just-in-time compilation):JIT 即時編譯技術(shù)是在運行時(runtime)將調(diào)用的函數(shù)或程序段編譯成機器碼載入內(nèi)存,以加快程序的執(zhí)行。說白了,就是在第一遍執(zhí)行一段代碼前,先執(zhí)行編譯動作,然后執(zhí)行編譯后的代碼。
上面只是簡單列出了兩點,當然還有更多的原因,限于篇幅就不再具體介紹,而我們開篇提到的 numba
就是通過 JIT 加速了 python 代碼。那么怎么使用 numba 加速我們的代碼呢?我們可以看一些簡單的例子:
2.numba 加速 python 的小例子
用 numba
加速 python 代碼多簡單方便呢,我們先來看看如何使用 numba 加速 python 代碼:
如果讓你用單純的 python 計算一個矩陣所有元素的和,很容易可以寫出下面的代碼:
def cal_sum(a):? ? ? result = 0? ? ? for i in range(a.shape[0]):? ? ? ? ? for j in range(a.shape[1]):? ? ? ? ? ? ? result += a[i, j]? ? ? return result?
當需要計算的矩陣很小的時候,貌似速度也不慢,可以接受,但是如果輸入的矩陣大小為 (500, 500),
a = np.random.random((500, 500))? %timeit cal_sum(a)?
輸出結(jié)果為:
47.8 ms ± 499 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
我們嘗試加上 numba:
import numba ? ? @numba.jit(nopython=True)? def cal_sum(a):? ? ? result = 0? ? ? for i in range(a.shape[0]):? ? ? ? ? for j in range(a.shape[1]):? ? ? ? ? ? ? result += a[i, j]? ? ? return result?
輸入同樣大小的矩陣
a = np.random.random((500, 500))? %timeit cal_sum(a)?
輸出結(jié)果為:
236 µs ± 545 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
注意在這里我們使用了%itemit
測試運行時間(原因我們留到后面說),通過對比兩個時間,我們可以發(fā)現(xiàn)通過 numba 獲得了非常明顯的加速效果!
我們來具體看一下如何用 numba
加速 python 代碼:在實際使用過程中,numba 其實是以裝飾器的形式加在 python 函數(shù)上的,用戶可以不用關(guān)心到底 numba 是通過什么方法來優(yōu)化代碼,只需要調(diào)用就行。同時需要注意到 @jit 裝飾器同時也有一個參數(shù) nopython, 這個參數(shù)主要是來區(qū)分 numba 的運行模式,numba 其實有兩種運行模式:一個是 nopython 模式,另一個就是 object模式。只有在nopython 模式下,才會獲得最好的加速效果,如果 numba 發(fā)現(xiàn)你的代碼里有它不能理解的東西,就會自動進入 object 模式,保證程序至少是能夠運行的(當然這其實就失去了添加 numba 的意義)。如果我們將裝飾器改為 @jit(nopython=True)
或者 @njit
,numba 會假設(shè)你已經(jīng)對所加速的函數(shù)非常了解,強制使用加速的方式,不會進入 object 模式,如編譯不成功,則直接拋出異常。
當然說到這里,可能大家還是很困惑,numba 到底是怎么加速 python
代碼的?
python 代碼的編譯過程包括四個階段:詞法分析 -> 語法分析 -> 生成字節(jié)碼 -> 將字節(jié)碼解釋為機器碼執(zhí)行, 常見的 python 解釋器的類型有 cpython、IPython、PyPy、Jython、IronPython,與其他解釋器不同,numba 是使用 LLVM 編譯技術(shù)來解釋字節(jié)碼的。
LLVM 是一個編譯器,它采用字節(jié)碼,并將其編譯為機器碼,編譯過程涉及許多額外的傳遞,而 LLVM編譯器可以優(yōu)化字節(jié)碼,例如某些頻繁執(zhí)行的模塊,LLVM 可以將其作為 “hot code” 從而進行相應(yīng)的優(yōu)化,LLVM 工具鏈非常擅長優(yōu)化字節(jié)碼,它不僅可以編譯 numba 的代碼,還可以優(yōu)化它。
在第一次調(diào)用 numba
裝飾的函數(shù)時,numba 將在調(diào)用期間推斷參數(shù)類型,numba 會結(jié)合給定的參數(shù)類型將其編譯為機器代碼。這個過程是有一定的時間消耗的,但是一旦編譯完成,numba 會為所呈現(xiàn)的特定類型的參數(shù)緩存函數(shù)的機器代碼版本,如果再次使用相同的類型調(diào)用它,它可以重用緩存的機器代碼而不必再次編譯。
- 在測量性能時,如果只使用一個簡單的計時器來計算一次,該計時器包括在執(zhí)行時編譯函數(shù)所花費的時間,最準確的運行時間應(yīng)該是第二次及以后調(diào)用函數(shù)的運行時間。
- 對于指定輸入類型這個問題,我們可以嘗試做一個簡單的實驗看看到底有怎樣的影響:
a = np.random.random((5000, 5000))? ? # 第一次調(diào)用時間包括編譯時間? start = time.time()? cal_sum(a)? end = time.time()? print("Elapsed (with compilation) = %s" % (end - start))? ? # 函數(shù)被編譯,機器代碼被緩存? start = time.time()? cal_sum(a)? end = time.time()? print("Elapsed (after compilation) = %s" % (end - start))? ? # 這里 a 本身的類型為 np.float64? b = a.astype(np.float32)? ? # 調(diào)用相同的函數(shù),但是輸入數(shù)據(jù)的類型變?yōu)?np.float32? start = time.time()? cal_sum(b)? end = time.time()? print("Elapsed (after compilation) = %s" % (end - start))?
輸出結(jié)果:
Elapsed (with compilation) = 0.20406198501586914
Elapsed (after compilation) = 0.025263309478759766
Elapsed (after compilation) = 0.07892274856567383
可以看到如果我們輸入了和第一次調(diào)用編譯時不同的數(shù)據(jù)類型,函數(shù)的運行時間也會有一個很明顯的增加,但仍然是遠低于第一次運行時的編譯的時間。
3. 如果調(diào)用 numba
的時候顯式地指定輸入、輸出數(shù)據(jù)的類型,可以加快初次調(diào)用的函數(shù)時的編譯速度,同時壞處就是如果顯式指定后,那么之后調(diào)用該函數(shù)都必須滿足規(guī)定的數(shù)據(jù)類型。
a = np.random.random((500, 500))? ? @numba.njit()? def cal_sum1(a):? ? ? result = 0? ? ? for i in range(a.shape[0]):? ? ? ? ? for j in range(a.shape[1]):? ? ? ? ? ? ? result += a[i, j]? ? ? return result? ? @numba.njit('float64(float64[:, :])')? def cal_sum2(a):? ? ? result = 0? ? ? for i in range(a.shape[0]):? ? ? ? ? for j in range(a.shape[1]):? ? ? ? ? ? ? result += a[i, j]? ? ? return result? ? # 不指定輸入輸出數(shù)據(jù)類型,讓 numba 自己判斷? start = time.time()? cal_sum1(a)? end = time.time()? print("Elapsed (with compilation) = %s" % (end - start))? ? # 指定輸入輸出數(shù)據(jù)類型? start = time.time()? cal_sum2(a)? end = time.time()? print("Elapsed (with compilation) = %s" % (end - start))?
分別耗時:
Elapsed (after compilation) = 0.054465532302856445? Elapsed (after compilation) = 0.0004112720489501953?
可以看到編譯的時間被大大減少了,其實這個時間非常接近直接運行該函數(shù)生成的機器代碼的時間。
上面說了這么多,但是轉(zhuǎn)念一想,矩陣相加這個函數(shù) numpy 里好像早就有了,np.sum 它不好用,它不香嘛??干嘛搞得這么復(fù)雜?
好吧,就上面舉的簡單的例子來說,使用 numpy
和 numba
加速基本效果差不多,但是在實際情況里面,不是所有的 for 循環(huán)代碼都可以直接用 numpy
自帶的函數(shù)實現(xiàn)。但是 numba 基本對所有的 for 循環(huán)代碼都有非常好的加速效果,當然前提是 for 循環(huán)里面的代碼必須是 numba 能夠理解的。
而在從實際使用中,一般推薦將代碼中密集的計算部分提取出來作為單獨的函數(shù)實現(xiàn),并使用 nopython
方式優(yōu)化,這樣可以保證我們能使用到 numba
的加速功能。其余部分還是使用 python 原生代碼,這樣一方面就可以做到在 numba 加速不明顯或者無法加速的代碼中調(diào)用各種函數(shù)實現(xiàn)自己的代碼邏輯, 另一方面也能享受到 numba
的加速效果。
3.numba 加速 numpy 運算
上面說了 numba 一大亮點就是加速 for 循環(huán),除此以外,numba 對 numpy 的運算也同樣的有加速的效果。因為即使是 numpy 也沒有 numba 轉(zhuǎn)換為機器碼快,numba 尤其擅長加速 numpy 的基本運算 (如加法、相乘和平方等等) ,其實準確來說如果 numpy 函數(shù)是對各個元素采用相同的操作的情況下,都會有比較好的效果。
我們簡單舉一個 numba 加速 numpy 運算的例子:
a = np.ones((1000, 1000), np.int64) * 5? b = np.ones((1000, 1000), np.int64) * 10? c = np.ones((1000, 1000), np.int64) * 15? ? def add_arrays(a, b, c):? ? ? return np.square(a, b, c)? ? @numba.njit? def add_arrays_numba(a, b, c):? ? ? return np.square(a, b, c)? ? # 第一次調(diào)用完成編譯? add_arrays_numba(a)? ? # 函數(shù)被編譯,機器代碼被緩存? start = time.time()? add_arrays_numba(a)? end = time.time()? print("Elapsed (after compilation) = %s" % (end - start))? ? # 不使用 numba 加速? start = time.time()? add_arrays(a)? end = time.time()? print("Elapsed = %s" % (end - start))?
Elapsed (after compilation) = 0.002088785171508789
Elapsed = 0.0031290054321289062
當我們對 numpy
數(shù)組進行基本的數(shù)組計算,比如加法、乘法和平方,numpy 都會自動在內(nèi)部向量化,這也是它可以比原生 python 代碼有更好性能的原因。但是在特定情況下,numpy 的代碼也不會和優(yōu)化過的機器代碼速度一樣快,此時 numba
直接作用于 numpy 運算也能起到一定的加速效果。
另一個例子主要來自于MMDetection3D
,經(jīng)過一定的簡化,主要是用來計算將點的坐標 (x, y) 壓縮到給定的[x_min, y_min, x_max, y_max]
范圍內(nèi):
x = np.random.random((5000))*5000? y = np.random.random((5000))*5000? x_min = 0? x_max = 1000? y_min=0? y_max=2000? ? @numba.njit? def get_clip_numba(x, y, x_min, y_min, x_max, y_max):? ? ? z = np.stack((x, y), axis=1)? ? ? z[:, 0] = np.clip(z[:, 0], x_min, x_max)? ? ? z[:, 1] = np.clip(z[:, 1], y_min, y_max)? ? ? return z? ? def get_clip(x, y, x_min, y_min, x_max, y_max):? ? ? z = np.stack((x, y), axis=1)? ? ? z[:, 0] = np.clip(z[:, 0], x_min, x_max)? ? ? z[:, 1] = np.clip(z[:, 1], y_min, y_max)? ? ? return z? ? %timeit get_clip_numba(x, y, x_min, y_min, x_max, y_max)? %timeit get_clip(x, y, x_min, y_min, x_max, y_max)?
分別用時:
33.8 μs ± 12.2 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)? 57.2 μs ± 258 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)?
從實際情況來看, 并不是所有的 numpy
函數(shù)在使用 numba 后都能獲得比較好的加速效果,在某些情況下甚至會降低 numpy 的運行速度。因此,在實際使用過程中建議提前測試一下確認加速效果。通常將 numba 用于加速 numpy 的時候都是 for 循環(huán)和 numpy 一起使用的情況。 numba 對 numpy 的大部分常用的函數(shù)都做了支持。
4.numba 使用 CUDA 加速
numba 更厲害的地方就在于,我們可以直接用 python 寫 CUDA Kernel, 直接在 GPU 上編譯和運行我們的 Python 程序,numba 通過將 python 代碼直接編譯為遵循 CUDA 執(zhí)行模型的 CUDA 內(nèi)核和設(shè)備函數(shù)來支持 CUDA GPU 編程( 但是實際上 numba 目前支持的 CUDA API 很少,希望開發(fā)團隊能更肝一點~~~) ,為了節(jié)省將 numpy 數(shù)組復(fù)制到指定設(shè)備,然后又將結(jié)果存儲到 numpy 數(shù)組中所浪費的時間,numba 提供了一些函數(shù)來聲明并將數(shù)組送到指定設(shè)備來節(jié)省不必要的復(fù)制到 cpu 的時間。
常用內(nèi)存分配函數(shù):
cuda.device_array()
:在設(shè)備上分配一個空向量,類似于numpy.empty();cuda.to_device()
:將主機的數(shù)據(jù)拷貝到設(shè)備;cuda.copy_to_host()
:將設(shè)備的數(shù)據(jù)拷貝回主機;
我們可以通過一個簡單的矩陣相加的例子來看看通過 numba 使用 CUDA 加速的效果:
from numba import cuda # 從numba調(diào)用cuda import numpy as np import math from time import time ? @cuda.jit def matrix_add(a, b, result, m, n): ? ? idx = cuda.threadIdx.x + cuda.blockDim.x * cuda.blockIdx.x ? ? idy = cuda.threadIdx.y+ cuda.blockDim.y * cuda.blockIdx.y ? ? if idx < m and idy < n: ? ? ? ? result[idx, idy] = a[idx, idy] + b[idx, idy] ? ? m = 5000 n = 4000 ? x = np.arange(m*n).reshape((m,n)).astype(np.int32) y = np.arange(m*n).reshape((m,n)).astype(np.int32) ? # 拷貝數(shù)據(jù)到設(shè)備端 x_device = cuda.to_device(x) y_device = cuda.to_device(y) ? # 在顯卡設(shè)備上初始化一塊用于存放GPU計算結(jié)果的空間 gpu_result1 = cuda.device_array((m,n)) gpu_result2 = cuda.device_array((m,n)) cpu_result = np.empty((m,n)) ? threads_per_block = 1024 blocks_per_grid = math.ceil(m*n / threads_per_block) # 第一次調(diào)用包含編譯時間 start = time() matrix_add[blocks_per_grid, threads_per_block](x_device, y_device, gpu_result1, m, n) cuda.synchronize() print("gpu matrix add time (with compilation) " + str(time() - start)) start = time() matrix_add[blocks_per_grid, threads_per_block](x_device, y_device, gpu_result2, m, n) cuda.synchronize() print("gpu matrix add time (after compilation)" + str(time() - start)) start = time() cpu_result = np.add(x, y) print("cpu matrix add time " + str(time() - start))
運行時間分別為:
gpu matrix add time (with compilation) 0.15977692604064941
gpu matrix add time (after compilation) 0.0005376338958740234
cpu matrix add time 0.023023128509521484
在通過 numba
進行 CUDA 加速的時候,主要是通過調(diào)用@cuda.jit
裝飾器實現(xiàn),從結(jié)果可以看到 numba 通過調(diào)用 CUDA 明顯加速了 python
程序。
5.For 循環(huán)寫法的影響
下面的一段代碼截取自MMDetection3D
, 主要是用來判斷一系列點是否在一系列多邊形的內(nèi)部,
我們可以有如下的兩種寫法:
在 For 循環(huán)里面計算 vec1, 每次循環(huán)都需要訪問多邊形 polygon
變量
@numba.jit(nopython=True)? def points_in_convex_polygon1(points, polygon, clockwise=True):? ? ? # first convert polygon to directed lines? ? ? num_points_of_polygon = polygon.shape[1]? ? ? num_points = points.shape[0]? ? ? num_polygons = polygon.shape[0]? ? ? vec1 = np.zeros((2), dtype=polygon.dtype)? ? ? ret = np.zeros((num_points, num_polygons), dtype=np.bool_)? ? ? success = True? ? ? cross = 0.0? ? ? for i in range(num_points):? ? ? ? ? for j in range(num_polygons):? ? ? ? ? ? ? success = True? ? ? ? ? ? ? for k in range(num_points_of_polygon):? ? ? ? ? ? ? ? ? if clockwise:? ? ? ? ? ? ? ? ? ? ? vec1 = polygon[j, k] - polygon[j, k - 1]? ? ? ? ? ? ? ? ? else:? ? ? ? ? ? ? ? ? ? ? vec1 = polygon[j, k - 1] - polygon[j, k]? ? ? ? ? ? ? ? ? cross = vec1[1] * (polygon[j, k, 0] - points[i, 0])? ? ? ? ? ? ? ? ? cross -= vec1[0] * (polygon[j, k, 1] - points[i, 1])? ? ? ? ? ? ? ? ? if cross >= 0:? ? ? ? ? ? ? ? ? ? ? success = False? ? ? ? ? ? ? ? ? ? ? break? ? ? ? ? ? ? ret[i, j] = success? ? ? return ret?
在循環(huán)前預(yù)先計算好所有的 vec
@numba.jit(nopython=True)? def points_in_convex_polygon2(points, polygon, clockwise=True):? ? ? # first convert polygon to directed lines? ? ? num_points_of_polygon = polygon.shape[1]? ? ? num_points = points.shape[0]? ? ? num_polygons = polygon.shape[0]? ? ? # vec for all the polygons? ? ? if clockwise:? ? ? ? ? vec1 = polygon - polygon[:, np.array([num_points_of_polygon - 1] +? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?list(range(num_points_of_polygon - 1))), :]? ? ? else:? ? ? ? ? vec1 = polygon[:, np.array([num_points_of_polygon - 1] +? ? ? ? ? ? ? ? ? ? ? ? ?list(range(num_points_of_polygon - 1))), :] - polygon? ? ? ret = np.zeros((num_points, num_polygons), dtype=np.bool_)? ? ? success = True? ? ? cross = 0.0? ? ? for i in range(num_points):? ? ? ? ? for j in range(num_polygons):? ? ? ? ? ? ? success = True? ? ? ? ? ? ? for k in range(num_points_of_polygon):? ? ? ? ? ? ? ? ? vec = vec1[j,k]? ? ? ? ? ? ? ? ? cross = vec[1] * (polygon[j, k, 0] - points[i, 0])? ? ? ? ? ? ? ? ? cross -= vec[0] * (polygon[j, k, 1] - points[i, 1])? ? ? ? ? ? ? ? ? if cross >= 0:? ? ? ? ? ? ? ? ? ? ? success = False? ? ? ? ? ? ? ? ? ? ? break? ? ? ? ? ? ? ret[i, j] = success? ? ? return ret?
簡單測試一下兩種寫法的速度:
points = np.random.random((20000, 2)) * 100? polygon = np.random.random((1000, 100, 2)) * 200 ? ? start = time.time()? points_in_convex_polygon1(points, polygon)? end = time.time()? print("Elapsed (with compilation) = %s" % (end - start))? ? start = time.time()? points_in_convex_polygon1(points, polygon)? end = time.time()? print("Elapsed (after compilation) = %s" % (end - start))? ? start = time.time()? points_in_convex_polygon2(points, polygon)? end = time.time()? print("Elapsed (with compilation) = %s" % (end - start))? ? start = time.time()? points_in_convex_polygon2(points, polygon)? end = time.time()? print("Elapsed (after compilation) = %s" % (end - start))?
輸出時間:
Elapsed (with compilation) = 3.9232356548309326
Elapsed (after compilation) = 3.6778993606567383
Elapsed (with compilation) = 0.6269152164459229
Elapsed (after compilation) = 0.22288227081298828
通過測試我們可以發(fā)現(xiàn)第二種方案會更快,在實際使用的時候,我們可以盡量減少在 for 循環(huán)內(nèi)部內(nèi)存的訪問次數(shù),從而降低函數(shù)的運行時間。
總結(jié) :
我們介紹了一些用 numba
加速的常見場景,能夠有效地提高我們代碼的速度。不過大家在使用的時候,建議多多嘗試,比較一下使用與不使用的速度區(qū)別(有時候用了 numba 還可能變得更慢......),此外 MMDetection3D 很早就使用了 numba 加速代碼,而且我們最近在 MMDetection3D
中升級了 numba 的版本,從而獲得更好的 numpy 兼容性和代碼加速效果,
到此這篇關(guān)于Python 提速器numba的文章就介紹到這了,更多相關(guān)Python numba內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vscode搭建之python?Django環(huán)境配置方式
這篇文章主要介紹了vscode搭建之python?Django環(huán)境配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01python 調(diào)用API接口 獲取和解析 Json數(shù)據(jù)
這篇文章主要介紹了python 如何調(diào)用API接口 獲取和解析 Json數(shù)據(jù),幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-09-09Python爬取騰訊疫情實時數(shù)據(jù)并存儲到mysql數(shù)據(jù)庫的示例代碼
這篇文章主要介紹了Python爬取騰訊疫情實時數(shù)據(jù)并存儲到mysql數(shù)據(jù)庫的示例代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03Python關(guān)于excel和shp的使用在matplotlib
今天小編就為大家分享一篇關(guān)于Python關(guān)于excel和shp的使用在matplotlib,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01Python實現(xiàn)葵花8號衛(wèi)星數(shù)據(jù)自動下載實例
這篇文章主要為大家介紹了Python實現(xiàn)葵花8號衛(wèi)星數(shù)據(jù)自動下載實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10Python gevent協(xié)程切換實現(xiàn)詳解
這篇文章主要介紹了Python gevent協(xié)程切換實現(xiàn)詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-09-09