`
cryolite
  • 浏览: 581334 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

小试NIF(上)

阅读更多
NIF是Erlang OTP R13B03版引入的,在这一版中还只是一个实验特性,按照原计划,NIF在R14B版成为正式特性,相应的API也将在该版之后稳定下来。等不及了,先试试再说。

1. 基本原理
最大的好处是速度。Erlang程序的逻辑当然是用Erlang写的,速度上不能和C比。NIF使我们可以用C实现相同的程序逻辑, 而速度则是C的速度。

简单的说就是将C实现的程序编译成动态共享对象(shared object)后动态加载到Erlang节点中,与Erlang共享内存空间,这与内联驱动(linked driver)有点类似,因此也就同样危险:有缺陷的代码会使整个Erlang节点当掉。

此外,在NIF函数中也不适合做那种太耗时的计算,不然会影响Erlang虚拟机的响应。

2. NIF编程模式
业务逻辑代码一般是在erlang函数中实现,这些函数一般是erlang写的(听上去像废话),作为一门高级的函数语言,Erlang在运行效率上是不能与C比。不过,有了NIF,如果我们对某些erlang函数的效率不满意可以用C的实现替代Erlang实现。

我的理解是:在实现上,某个Erlang模块的某些逻辑功能可以由一个基于NIF的C模块实现,具体来讲就是erlang模块中的某个或某些erlang函数可以对应C模块中一个或多个C函数。这些erlang函数不一定非得export给外界,也可以是模块私有的(但是如果该模块的私有函数没有被其它函数调用则在编译时可能会被编译器优化掉,这种情况下会导致装载NIF库失败)。

这需要告诉Erlang,哪些erlang函数有C版本的NIF实现,在NIF中,每个这样的erlang函数-c函数映射关系由一个C的数据结构(ErlNifFunc)表示,如下:
typedef struct {
    const char* name;
    unsigned arity;
    ERL_NIF_TERM (*fptr)(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
} ErlNifFunc;

第一个结构成员name表示对应的要替换掉的erlang函数名,第二个结构成员arity是此erlang函数的参数个数,这两个结构成员就确定了要替换掉的erlang函数;第三个结构成员是进行替换的C实现函数(NIF)。

可以看到,所有进行替换的C函数有着特定、统一的定义格式:
  • C函数名字当然可以随便取,不过最好与对应的erlang函数相关;
  • C函数第一个参数总是ErlNifEnv,代表着函数调用的上下文环境, 可以通过它得到对应的NIF模块的某些特定数据;
  • C函数第二个参数argc对应着Erlang函数的参数个数(erlang中是通过函数参数数量的不同来区分同名函数的);
  • C函数的第三个参数argv,按顺序一一对应着erlang函数的参数,参数类型都是统一的ERL_NIF_TERM数据结构的数组。C的数据结构ERL_NIF_TERM对应着erlang中的term,而所有的Erlang数据类型,无论是atom,整型,浮点数,tuple还是list,binary都统一叫term。,数组大小由钱一个参数argc决定,注意数组元素是const的;
  • C函数的返回值类型都是ERL_NIF_TERM。


当然,也可以在一个C函数中可以实现多个不同arity大小的erlang函数的业务逻辑。例如根据argc的个数做switch逻辑分支。

最终,通过ERL_NIF_INIT宏将C实现和对应的erlang模块绑定起来,实现NIF的初始化:
ERL_NIF_INIT(MODULE, ErlNifFunc funcs[], load, reload, upgrade, unload)

MODULE是对应的erlang模块名字,直接用模块名(不要字符串),funcs是NIF中用C实现的相关函数。 load, reload, upgrade, unload是在NIF相关生命周期中调用的C语言的回调函数。

新版本的erlang还提供了一个新的on_load指令(directive)用于在模块装载时自动调用某个函数:
-on_load(FunName/0).

该函数如果调用成功必须返回ok(表示模块正确装载),否则返回其它。一般通过on_load指定的函数在启动时自动调用erlang:load_nif(Path, LoadInfo)装载NIF模块实现。一个例子:
-on_load(init/0).
init() ->
    erlang:load_nif("./hello_nif", 0).


3. hello nif
3.1 一个hello world的例子
erlang模块代码:
-module(hello).
-export([say/0, on_load/0]).

-on_load(on_load/0).

on_load() ->
    erlang:load_nif("./hello", 0). 

say() ->
    "hello, i'm from erlang".

NIF实现代码:
#include "erl_nif.h"

static ERL_NIF_TERM say(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
    return enif_make_string(env, "hello, i'm from C NIF", ERL_NIF_LATIN1);
}

static ErlNifFunc nif_funcs[] =
{
    {"say", 0, say}
};

ERL_NIF_INIT(hello, nif_funcs, NULL, NULL, NULL, NULL);

3.2 编译
Linux下:
gcc -fPIC -shared -o hello.so hello.c -I$ERL_ROOT/usr/include/


Mac OS下
gcc -fPIC -bundle -flat_namespace -undefined suppress -o hello.so hello.c -I$ERL_ROOT/usr/include


环境变量ERL_ROOT为erlang-otp的安装路径

4. erlang-c数据交换
如何在两种语言中表示逻辑上相同的数据是写NIF程序关键。对NIF来说,数据的交换分输入和输出两种。
4.1 基本数据的交换
这里涉及的一个主要问题是函数参数的传递和计算结果的返回:即函数调用时将Erlang传来的数据转换成C的,函数计算的结果返回时将C的数据转换成Erlang的。

在erlang中,无论是基本数据类型atom、浮点数、整数,还是复合数据类型tuple, list,都统一被称为term。在NIF的C实现函数中,数据类型ERL_NIF_TERM对应Erlang中的这些term数据。

因此,所有的输入和输出都由统一的ERL_NIF_TERM类型表示,最后所有的NIF的C函数就可以统一用
ERL_NIF_TERM func(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 

这样的形式定义了。其中argc表示输入参数的个数,argv数组表示对应的输入参数数据;函数返回值也是ERL_NIF_TERM类型的数据。

  • 对输入参数的处理,例如第一个输入到底是int的还是double的,这取决于程序逻辑的约定。虽然NIF也提供了一系列的enif_is_*函数进行判断,但主要靠程序员自己根据约定转换成C中具体的数据类型。这个转换过程是通过一系列enif_get_*函数完成的。早期版本(R13B)的NIF API还很简陋, 从Erlang term到C的数据转换所支持的基本数据类型只有int, unsigned long和char数组, binary,不过还支持list类型的复合数据。后续版本的NIF开始支持更多数据类型了,例如double;
  • 对输出(函数返回)的出来,要将C的数据类型转换成ERL_NIF_TERM,这是通过一系列enif_make_*函数完成的,这组API生产的ERL_NIF_TERM数据最好视为只读的(想想erlang的不变的变量)。从NIF返回给erlang的这些ERL_NIF_TERM数据将由erlang节点管理并负责垃圾回收;
  • 所有ERL_NIF_TERM数据的属于某个ErlNifEnv数据,这些ERL_NIF_TERM数据的生命周期都与某个ErlNifEnv数据对象的生命周期有关。


4.2 binary数据的交换
erlang和nif实现中最有趣的是binary数据的交换了。这种交换甚至能使erlang变量成为真的“变”量。
NIF实现:
#include "erl_nif.h"
#include <stdio.h>

static ERL_NIF_TERM change_bin(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
    ErlNifBinary bin;
    enif_inspect_binary(env, argv[0], &bin);
    for (int i=0; i<bin.size; ++i) {
        ++bin.data[i];
    }   
    char buf[256];
    sprintf(buf, "change_bin: size=%zu, ptr=%p", bin.size, bin.data);
    return enif_make_string(env, buf, ERL_NIF_LATIN1);
}

static ErlNifFunc nif_funcs[] =
{
    {"change_bin", 1, change_bin}
};

ERL_NIF_INIT(niftest,nif_funcs,NULL,NULL,NULL,NULL);

对应的erlang模块:
-module(niftest).
-export([change_bin/1]).

-on_load(init/0).

init() ->
    erlang:load_nif("./niftest", 0). 

change_bin(_Bin) ->
    erlang:error({"NIF not implemented in nif_test at line", ?LINE}).

运行测试:
2> Bin = <<1, 2, 3, 4, 5>>.
<<1,2,3,4,5>>
3> niftest:change_bin(Bin).
"change_bin: size=5, ptr=0x863548"
4> Bin.
<<2,3,4,5,6>>
5> niftest:change_bin(Bin).
"change_bin: size=5, ptr=0x863548"
6> Bin.
<<3,4,5,6,7>>

这段hack代码说明了在Erlang中的binary数据与NIF C中操作的是同一块内存的数据。
这种用法可能什么实际价值,因为无法改变Bin的大小。实际应用中不要这样用,应将ErlNifBinary数据视为只读的。手册说只有enif_alloc_binary或enif_realloc_binary分配的ErlNifBinary才能做修改,一般情况下ErlNifBinary都被nif函数(NIF API)视为只读数据。

4.3 ErlNifEnv环境对象
所有的ERL_NIF_TERM数据都由某个ErlNifEnv管理,后者代表一种环境,一种能持有(英文是host)Erlang term的环境。ERL_NIF_TERM的有效期取决于ErlNifEnv环境的有效期,环境不存在了ERL_NIF_TERM数据也就无效了。

ErlNifEnv环境对象的指针在很多NIF API中做为第一个参数传递进来.
有两种ErlNifEnv环境对象:进程绑定的环境和进程独立的环境。
  • 进程绑定环境:所有NIF实现函数的第一个参数传递的都是此类环境,所有NIF函数调用参数(其它参数)都将属于此环境对象。进程绑定环境对象还提供了相关Erlang调用进程的信息。进程绑定环境对象只在NIF调用时有效,也就是说在不同NIF执行过程中保存并传递进程绑定对象的指针是无意义的(而且很危险);
  • 进程独立环境:此类环境对象由API函数enif_alloc_env创建,可用于在不同NIF执行过程中存储term,也可以通过enif_send发送term。进程独立环境对象及其包含的term数据总是有效的,可以通过调用API函数enif_free_env显式的摧毁它。

term数据可以通过enif_make_copy在不同环境间传递拷贝。
分享到:
评论

相关推荐

    rustler编写erlang nif

    【标题】:“rustler编写erlang nif” 在Erlang生态系统中,Native Implemented Functions (NIFs) 是一种机制,允许开发者用其他语言(如C、C++或Rust)编写性能关键部分的代码,然后在Erlang虚拟机(VM)中调用。...

    erlang nif test

    NIFs是Erlang与C交互的重要手段,它允许Erlang代码调用C函数,执行无法或不适合在Erlang虚拟机(VM)上进行的计算任务,比如硬件操作、加密算法或者高性能的数据处理。 创建一个Erlang NIF通常涉及以下几个步骤: ...

    上古卷轴 NIF 格式工具集

    上古卷轴 NIF 格式工具集 内包含: 3D模型减面优化软件 MooTools PolygonCruncher 11.02 Win Maya 2016 Nif Plugin Updated 2 3-65421-2-3汉化版+扩展功能 批量自动测定图片转DDS 烘焙法线贴图

    美国NIF光学规范-RMS梯度的重要性(NIF-GRMS)

    NIF是一个大型惯性约束聚变实验装置,其性能很大程度上取决于数千个光学元件的质量。这些元件包括192条光束线中的大口径光学部件,它们必须达到极其严格的规格才能确保激光束的有效聚焦。 在评估光学元件质量时,有...

    erl_nif_rustler_过程宏写法

    在Erlang中,`NIF (Native Implemented Functions)` 是一种机制,允许开发者使用其他语言(如C或Rust)编写高效性能的代码,并在Erlang虚拟机中调用。本篇主要介绍如何利用Rust语言来开发Erlang NIF,并探讨`erl_nif...

    erl_nif 扩展erlang的另外一种方法

    **标题与描述解析** 标题"erl_nif 扩展erlang的...综上所述,文档"erl_nif 扩展erlang的另外一种方法"将深入探讨Erlang的NIF机制,帮助开发者理解如何利用C语言扩展Erlang的功能,同时涵盖相关的最佳实践和注意事项。

    USB.NIF可以解决硬件不能正常工作

    因此,在进行上述操作之前,建议先排除其他可能的原因,如检查USB端口是否正常、尝试在其他电脑上测试设备、更新操作系统和USB控制器驱动等。 在日常使用中,保持系统的定期更新和备份是预防这类问题的有效措施。...

    3Dmax2012 的nif插件

    能帮出3Dmax导入或导出nif格式文件

    Windows下使用NIF扩展Erlang完整例子

    Windows下使用NIF扩展Erlang完整例子,包含nif工程项目,erlang引用例子。 配套文章:http://blog.csdn.net/mycwq/article/details/17527485

    blender_nif_plugin-blender-2.5.9_politicalgdm_plugin_BoneByBone_

    plugin for blender Version 2.5.9 (26 Nov 2012)==============================* Import normals if present for the Blender True Normals patch (requested by kormgar).* New import option to disable bone ...

    NIF的浮与沉

    NIF,这个被世界瞩目、耗资35亿美元以期实现聚变点火的装置,DOE却报告说短期内(1-2年)在NIF上实现点火是不可能的,中期内(5年)也不确定。苦心研究多年的NIF项目就这样付之东流啦?世界上其他正在运行的激光核...

    SSE NIF Optimizer_汉化版

    传奇版转重制版mod材质转换工具,提供给有需要的玩家及mod开发者

    nifxml:nif.xml文件的存储库,其中包含nif文件格式说明

    《深入解析nif.xml:揭示nif文件格式的秘密》 在游戏开发领域,nif(NiNode Interchange Format)文件是一种广泛使用的3D模型数据格式,尤其在Bethesda Softworks的游戏如《上古卷轴》系列中。nif.xml文件是nif文件...

    5130美化包,白色nif

    5130美化包,白色nif,超喜欢的,舍不得删

    NIF5002NT3G-VB一款N沟道SOT223封装MOSFET应用分析

    在热性能上,NIF5002NT3G-VB的最大结壳热阻RthJA在无风冷却的条件下为40至50°C/W,这意味着在大电流工作时,良好的散热设计是必不可少的。最大功率耗散在25°C时为4W,而70°C时则降至3W。为了确保器件的长期可靠性...

    排版方案和切割方案.nif

    排版方案和切割方案.nif

    NIF5002NT1G-VB-MOSFET产品应用与参数解析

    其封装形式为SOT223,这是一种小型表面贴装封装,适合于空间有限的电路板上。 在绝对最大额定值方面,门极源电压VGS不应超过正负20伏,以防止对MOSFET造成损坏。在环境温度为25°C时,最大连续漏电流ID为4安培,而...

    bitwise:用于显示Erlang调度程序问题的Erlang NIF示例

    这些变体的运行方式如下: 一个示例exor_bad/2显示了一个异常的NIF,如果输入二进制文件足够大,它将在调度程序线程上占用太多时间,并运行数秒钟。 通常,NIF应该在调度程序线程上运行仅一毫秒或更短的时间。 另一...

    用于创建 Erlang NIF 函数的安全 Rust 桥

    盗贼文档|入门|例子Rustler 是一个用安全的 Rust 代码编写 Erlang NIF 的库。这意味着应该没有办法让 BEAM (Erlang VM) 崩溃。该库提供了用于生成与 BEAM 交互的样板的工具,处理 Erlang 术语的编码和解码,并在它们...

Global site tag (gtag.js) - Google Analytics