背景

  1. 之前都是在看C/Cpp,也在努力的学这两个语言。但是感觉太难了。语法太多了(主要还是人笨,学不过来)。
  2. 一直也在看rust,感觉rust写起来和python差不多优雅,而且写法和c++又是有点类似。
  3. 然后最近空闲时间就把《Rust程序设计语言》和《通过例子学Rust》两本书看完了。学到了不少新内容。
  4. 最近也发现一些新的包,比如一个叫polar包的,提供类pandas的功能,但是比pandas更快,这个包的底层是使用rust写的,并且python也能使用。那我就在想:“我能不能学习一下,看看怎么让python用rust编译的东西,或者说,如果使用rust加速python。” 这篇文章主要就是介绍一个简单的rust加速python的案例。(主要是翻译,原文已放在文末的参考链接中)。

步骤

创建一个rust包

cargo new pyext-myrustlib

然后使用vscode进入这个文件夹下,打开src文件夹,创建一个新文件:lib.rs

python 安装magent Python 安装 rust_rust

编辑Cargo.toml

  1. 依赖的是rust-cpython。当前版本是0.7(2022年4月)。
  2. 输出的是一个dylib。这个可以让python直接import。
[package]
name = "pyext-myrustlib"
version = "0.1.0"
edition = "2021"


[lib]
name="myrustlib"
crate-type=["dylib"]

[dependencies.cpython]
version="0.7"
features=["extension-module"]

编写src/lib.rs

  1. cpython导入宏。
  2. 调用Python,PyResult
  3. 构建一个count_doubles函数。
  • 3.1 这个函数第一个参数是Python,是对python解释器的引用,可以让rust使用python的GIL。
  • 3.2 函数的第二个参数是val,是一个字符串的引用。
  • 3.3 返回的对象是PyResult。即使到时候有异常,也可以让这个函数报错。
  • 3.4 更多的细节,其实可以看看rust官网上两本书。
  1. 使用宏py_module_initializer!给这个lib注册一个新的属性。比如这里就是给count_doubles函数添加了一个函数文档。
#[macro_use]
extern crate cpython;

use cpython::{Python, PyResult};

fn count_doubles(_py:Python, val:&str) -> PyResult<u64>{
    let mut total = 0u64;

    // there is an imporved version later on this post
    for (c1, c2) in val.chars().zip(val.chars().skip(1)) {
        if c1 == c2 {
            total += 1;
        }
    }
    Ok(total)
}


py_module_initializer!(libmyrustlib, initlibmyrustlib, PyInit_myrustlib, |py, m | {
    m.add(py, "__doc__", "This module is implemented in Rust")?;
    m.add(py, "count_doubles", py_fn!(py, count_doubles(val: &str)))?;
    Ok(())
});

build

  1. build一下
cargo build --release
  1. 看输出的结果

build后,会产生一个文件:

ls -la target/release/libmyrustlib*

python 安装magent Python 安装 rust_python 安装magent_02

看到上面的结果,就知道,这个时候已经生成了sod格式的文件。

接下来,把这个so结尾的文件复制到一个新的文件夹中。

我就是把这个so结尾的文件放在了mypythoncode文件夹中。

python 安装magent Python 安装 rust_Rust_03

运行python

我在mypythoncode文件夹,还创建了doubles.py文件,用来做评测。这个文件的代码如下:

import re
import string
import random
import libmyrustlib   #  <-- 这里就是我们要import的rust的打包后的文件 (libmyrustlib.so)


def count_doubles(val):
    """Count repeated pair of chars ins a string"""
    total = 0
    for c1, c2 in zip(val, val[1:]):
        if c1 == c2:
            total += 1
    return total


double_re = re.compile(r'(?=(.)\1)')


def count_doubles_regex(val):
    return len(double_re.findall(val))


val = ''.join(random.choice(string.ascii_letters) for i in range(1000000))


def test_pure_python(benchmark):
    benchmark(count_doubles, val)


def test_regex(benchmark):
    benchmark(count_doubles_regex, val)


def test_rust(benchmark):   #  <-- 测试rust打包的函数的速度
    benchmark(libmyrustlib.count_doubles, val)

接下来我们比较各个函数的运行效率。
如果没有安装pytest。需要先安装一下,不然python程序跑不起来:pip install pytest-benchmark.

benchmark

mypythoncode文件夹下,运行:

pytest doubles.py

python 安装magent Python 安装 rust_rust_04

在mean这一列,会发现rust写的函数运行时间python写的函数运行时间的1/25倍左右。差距非常大。

python 安装magent Python 安装 rust_python 安装magent_05

注意事项:⚠️

  1. 本文不是一个比较性能的文章。因此上面的运行效率参考可能没什么价值,实际上,如果使用numpy计算的话,时间应该是rust版本的2倍。而不是25倍。
  2. 大家最好看一下原作者的内容【在参考链接[1] 中】. 原作者中一下代码和依赖的版本和现在不一样了,如果要说看代码部分,看我这个部分就行了。
  1. 目前还在学习rust中,只是把《Rust程序设计语言》和《通过例子学Rust》两本书看完了,代码都敲了一遍。说实话,还是非常喜欢这个语言的。感觉大部分代码写法和python差不多。后面应该还是会继续深入学习。
  2. 未来会继续把时间投资在pythonrust上。学习算法、学习科学计算。