利用Rust實現(xiàn)Python加速的技巧分享
背景
長期以來,Python由于易上手,有GC且生態(tài)強大等特點被廣泛使用,可是漸漸的人們也發(fā)現(xiàn)了它的不足,解釋型語言的運行速度終究比不過編譯型,況且由于Python設(shè)計時的動態(tài)數(shù)據(jù)類型一切皆對象(內(nèi)存都分配在堆上)等思想,也導(dǎo)致了運行速度緩慢.
隨著實時性要求的不斷提升,在一些計算量大要求快速響應(yīng)的場景傳統(tǒng)的Python就很難滿足要求,所以隨之慢慢有了各種解決辦法:
- 用更高效的解釋器
- 用jit即時編譯加速
- 改寫成Cython加速
- 對GIL動手,提高多線程性能
- …
其中目前使用最廣泛,最有效的應(yīng)該是jit與Cython這兩種方案,jit即時編譯可以將部分需要解釋的代碼直接轉(zhuǎn)為機器碼從而實現(xiàn)加速(減少解釋時開銷);而Cython更絕直接將Python原地升級,得到一個Cython這個Python與C的混血,可以通過Cython將代碼翻譯成C/C++的代碼再編譯成動態(tài)庫文件供使用.可是這樣就會造成Python原本的語法被改的“四不像”,這些后面慢慢再談.
既然都用上動態(tài)庫了,為什么不直接使用C++或者其他高效語言實現(xiàn),然后供Python調(diào)用呢?說到底,Cython不也是翻譯成C/C++的代碼編譯使用,只是為了方便Python開發(fā)人員才設(shè)計了這種類似于Python的語法.如果熟悉其他語言的話完全可以直接使用其他語言實現(xiàn)而不影響Python的基本語法.所以今天就來討論一下關(guān)于使用Rust對Python計算進行加速的問題.
Rust加速Python計算
首先,來看看Rust實現(xiàn)和Python實現(xiàn)基本的速度對比,目標是求斐波那契數(shù)列第n項的值.其中實現(xiàn)均采用遞歸調(diào)用,為了突出時間差異這里求第30項的值并重復(fù)50次
Python的實現(xiàn)與耗時如下:
import time
?
def fib(n:int) ->int:
? ?assert n>=0
? ?if n <= 1:
? ? ? ?return n
? ?return fib(n-1)+fib(n-2)
?
def main(test_times=50):
? ?start = time.time()
? ?for _ in range(test_times):
? ? ? ?fib(30)
? ?print(f"time cost {time.time()-start} s")
?
if __name__ == '__main__':
? ?main()
Rust的實現(xiàn)與耗時如下:
use std::time;
?
fn fib(n:i32)->u64{
? ?if n<=0{
? ? ? ?panic!("{} must be a postive number!",n);
? }
? ?match n{
? ? ? ?1|2 => 1,
? ? ? ?_ => fib(n-1) + fib(n-2)
? }
}
?
fn main() {
? ?let test_times = 50;
? ?let start = time::Instant::now();
? ?for i in 0..test_times{
? ? ? ?fib(30);
? }
? ?println!("time cost {:?}",start.elapsed())
}
?
這差異,足足一百多倍.那看來使用Rust提速是完全可行的,那怎么將Rust與Python相結(jié)合呢?或者如何把Rust的代碼編譯供Python調(diào)用,這個時候可以使用pyo3,首先安裝一下maturin工具pip install maturin,然后配置一下項目的Cargo.toml
[package]
name = "speedup_python"
version = "0.1.0"
edition = "2021"
?
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
?
[lib]
name = "speed_python"
crate-type = ["cdylib"]
?
[dependencies]
pyo3 = { version = "0.19.2", features = ["extension-module"] }這里編譯類型就設(shè)置為lib,關(guān)于多種不同lib類型的區(qū)別可以去看看Rust專欄之前的內(nèi)容.這里的name就是未來Python中調(diào)用的名字,下面再編寫lib.rs
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
?
#[pyfunction]
pub fn fib(n:i32)->u64{
? ?if n<=0{
? ? ? ?panic!("{} must be a postive number!",n);
? }
? ?match n{
? ? ? ?1|2 => 1,
? ? ? ?_ => fib(n-1) + fib(n-2)
? }
}
?
#[pymodule]
fn speed_python(_py:Python,m:&PyModule)->PyResult<()>{
? ?m.add_wrapped(wrap_pyfunction!(fib))?;
? ?Ok(())
}邏輯代碼基本沒有更改,只是添加了Rust實現(xiàn)Python module的代碼,這里的module name必須和toml中設(shè)置的name保持一致,否則也會無法導(dǎo)入.
最后運行maturin develop就可以實現(xiàn)編譯,給Python調(diào)用了.
加速對比
現(xiàn)在,我們已經(jīng)實現(xiàn)了Rust的加速,是不是非常簡單而且調(diào)用的時候可以使用原本的Python語法而不用進行任何更改.下面就來對比一下原始,numba,Cython,Rust四種方式的速度對比.
其中Cython實現(xiàn)cpy_fib.pyx如下
cpdef int c_fib(n:int): ? ?assert n>0 ? ?if n in [1, 2]: ? ? ? ?return 1 ? ?else: ? ? ? ?return c_fib(n - 1) + c_fib(n - 2)
然后寫setup進行編譯
from distutils.core import setup,Extension from Cython.Build import cythonize ? setup( ? ?ext_modules=cythonize(Extension( ? ? ? ?'cpy_fib', ? ? ? ?sources=['./cpy_fib.pyx'], ? ? ? ?language='c' ? )), )
運行python setup.py build_ext --inplace編譯,最后整體對比
import speed_python
import time
from cpy_fib import c_fib
from numba import jit
?
?
@jit(nopython=True)
def fib_jit(n: int) -> int:
? ?assert n > 0
? ?if n in [1, 2]:
? ? ? ?return 1
? ?else:
? ? ? ?return fib_jit(n - 1) + fib_jit(n - 2)
?
def fib(n: int) -> int:
? ?assert n > 0
? ?if n in [1, 2]:
? ? ? ?return 1
? ?else:
? ? ? ?return fib(n - 1) + fib(n - 2)
?
?
def test_speed(func,func_name:str,test_times=50):
? ?start = time.time()
? ?for _ in range(test_times):
? ? ? ?func(30)
? ?print(f"{func_name} speed up time cost {time.time() - start} s")
?
?
def main(test_times=50):
? ?test_speed(fib,"origin python")
? ?test_speed(fib_jit,"numba python")
? ?test_speed(speed_python.fib,"rust")
? ?test_speed(c_fib,"Cython")
?
?
if __name__ == '__main__':
? ?main()
當然如果希望進一步加速,我們還是有辦法的.使用maturin develop --release生成Rust的調(diào)用,再來比較一下計算耗時,速度又提升了一倍多

| 加速方法 | 耗時(ms) |
|---|---|
| 原始 | 4144 |
| Numba | 441 |
| Cython | 804 |
| Rust | 178 / 61 |
這加速對比也證明了在一些計算任務(wù)中Rust能更高效的實現(xiàn),并且不影響原始Python的代碼語法或者結(jié)構(gòu),只需要編譯調(diào)用.完全可以分配不同開發(fā)人員同時開發(fā),最后整合測試調(diào)用即可.
到此這篇關(guān)于利用Rust實現(xiàn)Python加速的技巧分享的文章就介紹到這了,更多相關(guān)Python加速內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python?TypeError:?‘float‘?object?is?not?subscriptable錯
發(fā)現(xiàn)問題寫python的時候出現(xiàn)了這個錯,所以想著給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于Python?TypeError:?‘float‘?object?is?not?subscriptable錯誤的解決辦法,需要的朋友可以參考下2022-12-12
PyTorch 導(dǎo)數(shù)應(yīng)用的使用教程
這篇文章主要介紹了PyTorch 導(dǎo)數(shù)應(yīng)用的使用教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08
Python實現(xiàn)生成隨機數(shù)據(jù)插入mysql數(shù)據(jù)庫的方法
這篇文章主要介紹了Python實現(xiàn)生成隨機數(shù)據(jù)插入mysql數(shù)據(jù)庫的方法,涉及Python隨機字符串生成及數(shù)據(jù)庫連接、插入等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
Python機器學習應(yīng)用之工業(yè)蒸汽數(shù)據(jù)分析篇詳解
本篇文章介紹了如何用Python進行工業(yè)蒸汽數(shù)據(jù)分析的過程及思路,通讀本篇對大家的學習或工作具有一定的價值,需要的朋友可以參考下2022-01-01
PyTorch上實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)CNN的方法
本篇文章主要介紹了PyTorch上實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)CNN的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04

