Rust导出Python/Node.js可用的DLL库

Published: 2018-07-10

Tags: Rust

本文总阅读量

在处理一些计算密集型,或者系统交互较多的时候,使用编译后的程序,动态库效率会高不少,而且把相应功能封装成动态库可以便于复用,隐藏脚本语言的实现细节

一般制作DLL都是用C/C++等语言来写,不过现在又多了一种选择——Rust

创建项目:

cargo new toolib --lib

修改lib.rs文件,计算斐波那契数列:

fn fib(n: i32) -> i32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n - 1) + fib(n - 2),
    }
}

#[no_mangle]
pub extern fn fibonacci(n: i32) -> i32 {
    let r: i32 = fib(n);
    return r
}

pub关键字说明可以在模块外部调用该函数,extern让这个函数符合 C 调用函数的约束,另外,rust在编译时会改变函数的名称,为了让外部能够访问到这个函数,所以使用#[no_mangle]属性使编译器不修改函数的名称

Cargo.toml 中添加一个属性:

[lib]
name = "orz"
crate-type = ["cdylib"]

编译DLL

cargo build --release

使用 Python 调用:

import time
from ctypes import cdll

lib = cdll.LoadLibrary("target/release/toolib.dll")

t1 = time.time()
result = lib.fibonacci(35)
t2 = time.time()

print("====== Rust ======")
print("use time: " + str(round(t2 - t1, 2)) + "s")
print("result is: " + str(result))

def F(n):
    if n == 0: return 0
    elif n == 1: return 1
    else: return F(n-1)+F(n-2)

t1 = time.time()
result = F(35)
t2 = time.time()

print("====== Python ======")
print("use time: " + str(round(t2 - t1, 2)) + "s")
print("result is: " + str(result))

运行:

λ python load.py
====== Rust ======
use time: 0.08s
result is: 9227465
====== Python ======
use time: 6.59s
result is: 9227465

λ node load.js
====== Rust ======
use time: 0.07s
result is:  9227465
done!
====== Node.js ======
use time: 0.18s
result is:  9227465

等等,Node.js 怎么比 Python 快这么多,这有点不科学,搜索后知道是 JIT compiler 的原因,Node.js运行时发现重复循环的代码块,会把它编译成汇编代码,Python默认解释器老老实实的运行代码,所以有很大的速度差异

换用Pypy编译试试:

====== Rust ======
use time: 0.09s
result is: 9227465
====== Python ======
use time: 0.29s
result is: 9227465

嗯,还差一丢丢,不过已经在一个量级了

后注:

crate_type 可选值有好几个,如:bin, lib, dylib, staticlib, cdylib等,具体解释可以参考Linkage

我在看的时候,比较疑惑dylibcdylib,因为文档上都标注了可以被外部程序或库调用,而且编译出的dll文件,经过测试,都是可以使用的,那么区别是什么呢

文末参考2的rfcs链接中有详细的介绍,如下罗列一下区别:

  • Metadata: dylib保留有一些元数据,cdylib没有
  • Symbol visibility: 符号能见度,dylib打出的dll文件里有很多ABI函数,cdylib则只导出用户指定的函数
  • LTO:“LTO - this will disallowed for rdylibs, but enabled for cdylibs.” (注: 不懂)
  • Linkage: dylib默认会自动链接标准库,cdylib默认会自动链接所有依赖

使用Dependency Walker工具查看符号能见度

rust dll

另外,使用编译的cdylib风格的DLL明显比dylib风格的DLL小很多,本例中,dylib大小为885kb,cdylib的大小为10kb,体积有大幅的减小

参考: