python调用rust_使用Rust加速Python

Python具有开发快速的特点,但是在运行效率上比静态编译型语言慢不少,我们今天要介绍的Rust就是其中一种。

Rust是一种安全、并发、实用的编程语言,有着惊人的运行速度,能够防止段错误,并保证线程安全,使每个人都能够构建可靠、高效的软件。

当我们的Python程序出现性能瓶颈时,可以从如下几个方面优化:

优化算法,使用更高效率的算法来提升性能;

使用并发,如多线程程序;

使用编译型语言编写扩展;

优化网络、磁盘、数据库等。

性能优化是个大命题,我们需要从多个方面着手考虑,今天我介绍的是第3种方法,并且选择Rust语言。我们将编写一个so扩展供Python端调用。

这里不会讲Rust的入门,具体规范可以看官方文档或者中文文档:https://rustlang-cn.org/

我们选择Httpbin作为基准程序,进行修改然后对比效果。

1. 原始代码

为了简单,我稍微修改了view_get,使之只返回客户端的请求方法。如下:

1

2

3

{

"method": "GET"

}

代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# httpbin/core.py

@app.route("/get", methods=("GET",))

def view_get():

"""The request's query parameters.

---

tags:

- HTTP Methods

produces:

- application/json

responses:

200:

description: The request's query parameters.

"""

return jsonify(get_dict("method"))

2. 测试一下原始代码性能:

使用wrk进行benchmark测试:

1

2

3

4

5

6

7

8

9

10

11

wrk http://127.0.0.1:5000/get -c 400 -t 10

Running 10s test @ http://127.0.0.1:5000/get

10 threads and 400 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 314.77ms 121.47ms 1.98s 95.24%

Req/Sec 54.09 37.46 200.00 64.29%

4355 requests in 10.04s, 1.00MB read

Socket errors: connect 0, read 0, write 0, timeout 9

Requests/sec: 433.98

Transfer/sec: 101.71KB

可以看出:平均时延为315ms左右,RPS为434左右。

首先新建一个lib类型的项目,比如名字为handle:

1

cargo new handle --lib

这样就在当前项目下新建了一个目录:handle,我们需要编辑handle/Cargo.toml:

1

2

3

4

5

6

7

8

9

10

11

12

13

[package]

name = "handle"

version = "0.1.0"

authors = ["Joseph "]

edition = "2018"

[lib]

name = "handle"

crate-type = ["cdylib"]

[dependencies.cpython]

version = "0.2"

features = ["extension-module"]

然后是编辑handle/src/lib.rs:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#[macro_use]

extern crate cpython;

use cpython::ObjectProtocol;

use cpython::{PyObject, PyResult, Python};

use std::collections::HashMap;

fn ret_py_dict(py: Python, obj: PyObject) -> PyResult> {

let mut response = HashMap::new();

response.insert("method", obj.getattr(py, "method")?.extract(py)?);

Ok(response)

}

py_module_initializer!(handle, init_handle, PyInit_handle, |py, m| {

m.add(py, "ret_py_dict", py_fn!(py, ret_py_dict(val: PyObject)))?;

Ok(())

});

这里的handle是模块名,ret_py_dict就是模块的方法,供Python调用,使用方法:

1

2

import handle

handle.ret_py_dict(...)

然后需要编译此模块,我们使用Makefile编写编译规则:

1

2

3

4

5

build:

# 编译

cd handle && cargo build --release

# 将so文件拷贝到Python代码目录

cp handle/target/release/libhandle.so httpbin/handle.so

执行make命令编译并将so文件拷贝到指定目录。

4. Python端调用方法

编写一个view_get1方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

from . import handle

@app.route("/get1", methods=("GET",))

def view_get1():

"""The request's query parameters.

---

tags:

- HTTP Methods

produces:

- application/json

responses:

200:

description: The request's query parameters.

"""

resp = handle.ret_py_dict(request)

return jsonify(resp)

与我们的原始函数唯一区别是:jsonify函数的参数,即响应内容是由扩展模块产生,类型都是dict。

curl响应为:

1

2

3

4

5

curl localhost:5000/get1

{

"method": "GET"

}

结果一致。

5. 测试使用了扩展的性能

1

2

3

4

5

6

7

8

9

10

11

wrk http://127.0.0.1:5000/get1 -c 400 -t 10

Running 10s test @ http://127.0.0.1:5000/get1

10 threads and 400 connections

Thread Stats Avg Stdev Max +/- Stdev

Latency 247.73ms 101.96ms 1.91s 96.28%

Req/Sec 80.17 53.34 272.00 59.49%

5482 requests in 10.09s, 1.25MB read

Socket errors: connect 0, read 0, write 0, timeout 4

Requests/sec: 543.49

Transfer/sec: 127.38KB

可以看出:平均时延为248ms左右,RPS为544左右。

比原始版本:时延低70ms,RPS高110,效果比较明显,这里仅仅改写了获取对象属性的方式。

6. 总结

如果想继续优化,可以考虑改写jsonify函数。在这里我们提出了一个优化Python程序性能(Latency, RPS)的方案。前文也说到过,优化性能应该先从代码结构、算法方面做优化,当语言成为瓶颈时,使用Rust编写扩展实为一种好的方式。

参考:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值