用rust编写erlang的nif方案,以下几个star比较高
- Rustler ( https://github.com/hansihe/rustler ) 这个比较火,但是没有关于如何在 Erlang 中使用它的文档。作者主要是elixir + rust
- erlang-rust-nif ( https://github.com/erszcz/erlang-rust-nif ) 是 NIF 的低级实现和扩展开发方法。代码看起来像是 C 语言的基本翻译。程序集不是通用的,并且有边界条件。
- erlang_nif-sys ( https://github.com/goertzenator/erlang_nif-sys )。作为 Rustler 的基础运行的低级功能包,需要花费大量时间和精力来编写 NIF。
- bitwise_rust ( https://github.com/tatsuya6502/bitwise_rust )。这个演示了如何使用调度程序。它也是 ANSI C API 的包装器,没有语法糖。
上面列出了四个方案,
第一个方案,最初研究了下,没有erlang的详细例子,浅尝辄止,给跳过了。
第二个方案,一瞅最近一次被更新在6年前,老掉牙了,跳过。
第三个方案,弄了3天,放弃了,作者他在issue下都说了不更新了,erlang23什么的都不管了,别指望24及以后版本了,而且他写的例子,mynifmod的make_map方法(erl),在rust中是用enif_make_map_from_arrays,这方法早被弃用了,还挂在他那个github上,(╯‵□′)╯︵┻━┻
第四个方案,和方案二一样,最近一次更新在6年前。
研究无果,去crates.io上看看运气,一圈下来还是只有方案一人气最高
没办法,硬头皮上吧。
经个人长达一个周的爬坑跳坑,搜遍栈爆网和狗狗网,均未发现erlang吃螃蟹的正确操作,不得不使用终极绝招之猜代码拼参数的方式(这作者真没写太多详细的注释),终于给吃到螃蟹了。
Rust
使用cargo new 一个空项目出来,src目录下创建lib.rs
#![allow(deprecated)]
use rustler::{Encoder, Env, Error, Term};
mod atoms {
rustler::rustler_atoms! {
atom ok;
}
}
#[allow(dead_code)]
struct TestResource {
a: u32,
}
rustler::rustler_export_nifs! {
"t",
[
("add", 2, add),
],
Some(load)
}
fn add<'a>(env: Env<'a>, terms: &[Term<'a>]) -> Result<Term<'a>, Error> {
let a: u32 = terms[0].decode()?;
let b: u32 = terms[1].decode()?;
Ok((atoms::ok(), a + b).encode(env))
}
fn load(env: Env, _: Term) -> bool {
rustler::resource_struct_init!(TestResource, env);
true
}
这其中 t 是下文的 t.erl 的意思,照搬要注意。
rustler::rustler_export_nifs! {
"t", // 这个 "t" 是erlang模块的名称
[
("add", 2, add), // 这里是erlang调用的方法名,和参数个数,以及本地被调用rust方法名
],
Some(load)
}
这里只有nif_load,根据erlang nif 手册说明,还有nif_load nif_update nif_unload 方法,等我补充。( 见下文)
Cargo.toml
[package]
name = "t_nif"
version = "0.1.0"
authors = []
edition = "2018"
[lib]
name = "t_nif"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
rustler = "0.22.0"
不确定版本的话,上crates.io,传送门:https://crates.io/crates/rustler
Erlang
t.erl
个人喜欢简单点的名称,方便敲shell
-module(t).
-on_load(init/0).
-export([add/2]).
init() ->
ok = erlang:load_nif("./t_nif", 0).
add(_A,_B)->
none.
交互操作
rust这边,cargo build --release,到target目录找到 t_nif.dll 这文件,把dll拷贝到 t.erl 的同级目录,当然 nif 路径规划你说了算。
编译 + 调用,成功!
项目打包
已设置 不可调动态积分,0积分
https://download.csdn.net/download/ap114/19935913
老铁记得点赞收藏
3天后续(看这就对了,上面代码过时了)
【2021年7月6日19:03:55】
rustler 0.22.0 已经支持宏写法了,上面那一段代码是老版本的代码,过于复杂了,现在重写简化一下。
erlang
-module(t).
-on_load(init/0).
-export([sort_i64_asc/1,sort_i64_desc/1,appox_add/2]).
-export([test/1]).
init() ->
erlang:load_nif("./ap114_nif", 0).
test(Count) ->
BigData = generate(Count,[]),
%% rust
TimeA = milliseconds(),
RustNifResult = sort_i64_asc(BigData),
%% native erlang
TimeB = milliseconds(),
NativeErl = lists:sort(BigData),
TimeEnd = milliseconds(),
io:format("rust nif :~p ms\r\n",[TimeB-TimeA]),
io:format("native erl :~p ms\r\n",[TimeEnd-TimeB]),
io:format("result compare:~p\r\n",[RustNifResult =:= NativeErl]).
%% ----------------------------------------------------
%% Func: sort_i64_asc/1 (List)
%% Description: 使用nif进行排序,升序排序
%% rust内部实现:使用rust内部Timsort排序,且单个元素为i64
%% 最好 O(n) 最坏O(nlogn) 空间复杂度 T(n) 稳定排序
%% Argu: [1,9,4,7,6] -> [1,4,6,7,9]
%% Returns: {ok,TupleList}
%% ----------------------------------------------------
sort_i64_asc(_List) -> erlang:nif_error("NIF library not loaded").
%% ----------------------------------------------------
%% Func: sort_i64_desc/1 (List)
%% Description: 使用nif进行排序,降序排序
%% rust内部实现:使用rust内部Timsort排序,且单个元素为i64
%% 最好 O(n) 最坏O(nlogn) 空间复杂度 T(n) 稳定排序
%% Argu: [1,9,4,7,6] -> [9,7,6,4,1]
%% Returns: {ok,TupleList}
%% ----------------------------------------------------
sort_i64_desc(_List) -> erlang:nif_error("NIF library not loaded").
%% ----------------------------------------------------
%% Func: appox_add/2 (A,B)
%% Description: 一个简单的加法
%% rust 内部是 float 64 与 float 64 做加法,遵循ieee 754标准
%% 为了解决浮点数计算弄得
%% Note: 0.1+0.2 =:= 0.30000000000000004
%% 0.3 =:= appox_add(0.1,0.2)
%%
%% Argu: A ::float() | integer()
%% B :: float() | integer()
%% A + B
%% Returns: {ok,float()} float64
%% ----------------------------------------------------
appox_add(_A,_B) -> erlang:nif_error("NIF library not loaded").
%%%=======================Local Function======================
%% 随机N个元组
generate(0,R) ->
R;
generate(Num,R) ->
Rand = rand:uniform(50000),
generate(Num-1,[Rand|R]).
%% 取当前毫秒
milliseconds() ->
{MegaSecs, Secs, MicroSecs} = os:timestamp(),
1000000000 * MegaSecs + Secs * 1000 + MicroSecs div 1000.
rust
ap114.rs
// https://blog.csdn.net/ap114/article/details/118092301
// 一个简单的加法
#[rustler::nif]
fn appox_add(a:f64,b:f64) -> f64 {
((a+b) * 100.0).round() / 100.0
}
// i64升序排序
#[rustler::nif]
fn sort_i64_asc(erl_list:Vec<i64>) -> Vec<i64> {
let mut erl_list2 = erl_list;
erl_list2.sort();
erl_list2
}
// i64降序排序
#[rustler::nif]
fn sort_i64_desc(erl_list:Vec<i64>) -> Vec<i64> {
let mut erl_list2 = erl_list;
erl_list2.sort_by(|a, b| b.partial_cmp(a).unwrap());
erl_list2
}
lib.rs
#![allow(deprecated)]
pub mod test_resource;
pub mod ap114;
rustler::init! (
"t",
[
ap114::appox_add,
ap114::sort_i64_asc,
ap114::sort_i64_desc
],
load = load
);
fn load(env: rustler::Env, _load_info: rustler::Term) -> bool {
test_resource::on_load(env);
true
}
test_resource.rs
use rustler::{Env, ResourceArc};
use std::sync::RwLock;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct TestResource {
test_field: RwLock<i32>,
}
/// This one is designed to look more like pointer data, to increase the
/// chance of segfaults if the implementation is wrong.
pub struct ImmutableResource {
a: u32,
b: u32,
}
pub fn on_load(env: Env) -> bool {
rustler::resource!(TestResource, env);
rustler::resource!(ImmutableResource, env);
true
}
#[rustler::nif]
pub fn resource_make() -> ResourceArc<TestResource> {
ResourceArc::new(TestResource {
test_field: RwLock::new(0),
})
}
#[rustler::nif]
pub fn resource_set_integer_field(resource: ResourceArc<TestResource>, n: i32) -> &'static str {
let mut test_field = resource.test_field.write().unwrap();
*test_field = n;
"ok"
}
#[rustler::nif]
pub fn resource_get_integer_field(resource: ResourceArc<TestResource>) -> i32 {
*resource.test_field.read().unwrap()
}
lazy_static::lazy_static! {
static ref COUNT: AtomicUsize = AtomicUsize::new(0);
}
impl ImmutableResource {
fn new(u: u32) -> ImmutableResource {
COUNT.fetch_add(1, Ordering::SeqCst);
ImmutableResource { a: u, b: !u }
}
}
impl Drop for ImmutableResource {
fn drop(&mut self) {
assert_eq!(self.a, !self.b);
self.b = self.a;
COUNT.fetch_sub(1, Ordering::SeqCst);
}
}
#[rustler::nif]
pub fn resource_make_immutable(u: u32) -> ResourceArc<ImmutableResource> {
ResourceArc::new(ImmutableResource::new(u))
}
// Count how many instances of `ImmutableResource` are currently alive globally.
#[rustler::nif]
pub fn resource_immutable_count() -> u32 {
COUNT.load(Ordering::SeqCst) as u32
}
Cargo.toml
[package]
name = "ap114_nif"
version = "0.1.0"
authors = ["k@cnkizy.com"]
edition = "2018"
[lib]
name = "ap114_nif"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
rustler = "0.22.0"
lazy_static = "1.4"
性能检验
性能显著,不枉我搜爆栈爆网和狗狗网,汗水没白流
nif比erlang快3倍
那么,这就完了?接下来可以搞事了吗?
继续,我试着在rust里解析tuple list,这下栽了,很明显,复杂类型的erl数据对rust数据解析造成了很大性能浪费。详细代码就不放出了,有兴趣的小伙伴自己试着解析tuplist,在rust中,tuple list解析大致是这样的。
// 解析tuple list 并返回一个 []
#[rustler::nif]
fn test_tuple(erl_list:Vec<Term>) -> Vec<i64> {
vec![]
}
这下再来看性能比较
erlang比nif快3倍
新写法已打包 https://download.csdn.net/download/ap114/20047095
其他补充
坑1:rustler不支持热更
之前说过【这里只有nif_load,根据erlang nif 手册说明,还有nif_load nif_update nif_unload 方法,等我补充。】,其实rustler作者这里在创作之初就考虑到这问题了,
nif 重载会发生内存安全问题,详见: 传送门 -> https://github.com/rusterlium/rustler/issues/13
作者在2016年说nif重载被滥用会让内存变得非常不可控,会让之前安全的内存结构失效,
没有找到一个安全的方法,因此没提供热更新nif的方法
2017年说 提供unsafe upgrade:my_upgrade_fn向rustler_export_nifs,实现曲线热更nif
2019年仍然认为nif不提供热更好
外国码农当然也有一些骚操作,“nif不能卸载,但可通过版本控制,实现伪重载,
传送门 -> https://github.com/rusterlium/rustler/issues/276
所以跟着作者的思路,我就不研究热更了。
坑2:复杂的数据类型对nif不友好,rust解析会造成较大的浪费
使用rustler的时候发现一个性能问题,传输大量erlang数据到nif进行交互时,rust解析数据会浪费很多时间,最后发现erlang的代码反而比nif还快,所以考虑清楚什么时候下该使用nif,用了真的会比erlang原生代码好吗?
哇塞你都看到这里了,还不点个赞。
又过了N天的后续
2021年7月15日11:14:17
@sinat_18424991 大佬说他发现在rust中,rustler不用声明onload方法也可以编译通过,而且代码更简化了。
rust
lib.rs
#[rustler::nif]
fn add(a:i64,b:i64) -> i64 {
a + b
}
rustler::init!("t",[add]);
然后cargo.toml
[package]
name = "nif_simple"
version = "0.1.0"
authors = [""]
edition = "2018"
[lib]
name = "nif_simple"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
rustler = "0.22.0"
就这几句就是所有代码了