【erlang】吃螃蟹 rust 开发 erlang nif 的正确方式 rustler

用rust编写erlang的nif方案,以下几个star比较高

  1. Rustler ( https://github.com/hansihe/rustler ) 这个比较火,但是没有关于如何在 Erlang 中使用它的文档。作者主要是elixir + rust
  2. erlang-rust-nif ( https://github.com/erszcz/erlang-rust-nif ) 是 NIF 的低级实现和扩展开发方法。代码看起来像是 C 语言的基本翻译。程序集不是通用的,并且有边界条件。
  3. erlang_nif-sys ( https://github.com/goertzenator/erlang_nif-sys )。作为 Rustler 的基础运行的低级功能包,需要花费大量时间和精力来编写 NIF。
  4. 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"

就这几句就是所有代码了 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值