使用Rust加速Python

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

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

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

  1. 优化算法,使用更高效率的算法来提升性能;
  2. 使用并发,如多线程程序;
  3. 使用编译型语言编写扩展;
  4. 优化网络、磁盘、数据库等。

性能优化是个大命题,我们需要从多个方面着手考虑,今天我介绍的是第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左右。

3. 使用rust-cpython编写扩展

首先新建一个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 <josephok@qq.com>"]
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<HashMap<&'static str, String>> {
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编写扩展实为一种好的方式。

参考:

https://developers.redhat.com/blog/2017/11/16/speed-python-using-rust/

Golang时间处理

1. 将日期转换为Unix时间戳

1
2
3
4
5
6
7
8
9
10
11
// Time.Unix()
package main

import (
"log"
"time"
)

func main() {
log.Println(time.Now().Unix())
}

2. 将Unix时间戳转换为日期时间

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"log"
"time"
)

func main() {
timestamp := time.Now().Unix()
t := time.Unix(timestamp, 0)
log.Println(t)
}

Read More

用Hexo写博客并用Travis CI部署到Github Pages和Coding Pages

Hexo是用Nodejs实现的一个静态网站生成器。简单好用,我们可以使用它来生成我们的博客站点。

1. 安装Hexo

首先你必须安装Nodejs环境,如果没有安装的话,请参考其官方文档自行安装。
使用一行命令即可安装Hexo:

1
$ npm install -g hexo-cli

2. 生成站点文件

安装 Hexo 完成后,请执行下列命令,Hexo 将会在指定文件夹中新建所需要的文件。

1
2
3
$ hexo init <dir>
$ cd <dir>
$ npm install

这里的

替换成你自己的目录,比如我这里是blog

3. 配置Hexo

具体网站配置可以参考这里:https://hexo.io/zh-cn/docs/configuration.html
改成你自己想要的配置。

4. 写文章

你可以执行下列命令来创建一篇新文章。

1
$ hexo new [layout] <title>

您可以在命令中指定文章的布局(layout),默认为 post,可以通过修改 _config.yml 中的 default_layout 参数来指定默认布局。

Read More

Tornado源码解析之IOLoop

0. 简介

Tornado是一个用Python语言写成的Web服务器兼Web应用框架,由FriendFeed公司在自己的网站FriendFeed中使用,被Facebook收购以后框架以开源软件形式开放给大众。

tornado最大的特点就是其支持异步IO,所以它有着优异的性能。下表是和一些其他Web框架与服务器的对比:(处理器为 AMD Opteron, 主频2.4GHz, 4核) (来源wikipedia)

Read More

Bash 快捷键

生活在 Bash shell 中,熟记以下快捷键,将极大的提高你的命令行操作效率。

编辑命令

Ctrl + a :移到命令行首
Ctrl + e :移到命令行尾
Ctrl + f :按字符前移(右向)
Ctrl + b :按字符后移(左向)
ESC + f :按单词前移(右向)
Alt + b :按单词后移(左向)
Ctrl + xx:在命令行首和光标之间移动
Ctrl + u :从光标处删除至命令行首
Ctrl + k :从光标处删除至命令行尾
Ctrl + w :从光标处删除至字首
Alt + d :从光标处删除至字尾
Ctrl + d :删除光标处的字符
Ctrl + h :删除光标前的字符
Ctrl + y :粘贴至光标后
Alt + c :从光标处更改为首字母大写的单词
Alt + u :从光标处更改为全部大写的单词
Alt + l :从光标处更改为全部小写的单词
Ctrl + t :交换光标处和之前的字符
Alt + t :交换光标处和之前的单词
Alt + Backspace:与 Ctrl + w 相同类似,分隔符有些差别

重新执行命令

Ctrl + r:逆向搜索命令历史
Ctrl + g:从历史搜索模式退出
Ctrl + p:历史中的上一条命令
Ctrl + n:历史中的下一条命令
Alt + .:使用上一条命令的最后一个参数

控制命令

Ctrl + l:清屏
Ctrl + o:执行当前命令,并选择上一条命令
Ctrl + s:阻止屏幕输出
Ctrl + q:允许屏幕输出
Ctrl + c:终止命令
Ctrl + z:挂起命令

Bang (!) 命令

!!:执行上一条命令
!blah:执行最近的以 blah 开头的命令,如 !ls
!blah:p:仅打印输出,而不执行
!$:上一条命令的最后一个参数,与 Alt + . 相同
!$:p:打印输出 !$ 的内容
!*:上一条命令的所有参数
!*:p:打印输出 !* 的内容
^blah:删除上一条命令中的 blah
^blah^foo:将上一条命令中的 blah 替换为 foo
^blah^foo^:将上一条命令中所有的 blah 都替换为 foo

友情提示:

以上介绍的大多数 Bash 快捷键仅当在 emacs 编辑模式时有效,若你将 Bash 配置为 vi 编辑模式,那将遵循 vi 的按键绑定。Bash 默认为 emacs 编辑模式。如果你的 Bash 不在 emacs 编辑模式,可通过 set -o emacs 设置。
^S、^Q、^C、^Z 是由终端设备处理的,可用 stty 命令设置。