zoukankan      html  css  js  c++  java
  • Erlang NIF浅析

    在Erlang调用C代码时,NIF(Native Implemented Function)是比port driver更简单和有效的实现方式,尤其是编写同步程序中,NIF是非常适合Erlang 的。

    1,  基本原理

          NIF可以使我们可以用C实现相同的程序逻辑,但速度比用纯Erlang的快,跟C的速度很相近。

          C语言编译生成的动态库(*.so)在Erlang调用C模块时动态加载到Erlang的进程空间中,所以这是用Erlang调用C代码最高效的方式。调用NIF不用上下文的切换开销,但是安全性不是很高,因为NIF的crash会导致整个Erlang进程crash。

    2,  编程模式

          在用NIF编程过程中,业务逻辑的代码一般是用Erlang 编写的,由于虚拟机的原因,Erlang在运行效率上是不如C的,有了NIF之后,对于那些Erlang运行起来比较耗时的模块我们可以用C来实现。

         在用NIF时,我们要告知Erlang哪些函数是用C实现的,在NIF中,每个这样的Erlang-C映射函数由一个C的数据结构ErlNifFunc来表示:

    1. typedef struct  
    2. {  
    3.     const char* name;  
    4.     unsigned arity;  
    5.     ERL_NIF_TERM (*fptr)(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);  
    6. }ErlNifFunc;  

           在上面的类中,name表示要在Erlang中用C替换掉的函数(Erlang中调用的函数名),arity表示name这个函数的参数个数,fptr是一个指向函数的指针,它是name函数对应的C语言实现。

           在C语言中实现的NIF函数要有下面的定义方式:

    1. static ERL_NIF_TERM  FuncName(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])   

          这个函数接受由Erlang传过来的参数,参数列表是argv,参数个数是argc。在该函数中如果要使用传过来的参数,就要用enif_get_*系列函数将argv解析成对应的数据类型,如int, float, tuple, list 等等。

          最终,要通过ERL_NIF_INIT宏将C实现和对应的Erlang模块绑定起来,实现NIF的初始化:

    1. ERL_NIF_INIT(MODULE, ErlNifFunc funcs[], load, reload, upgrade, unload)  

           MODULE是对应的erlang模块的名字,直接用模块名,funcs是NIF中用C实现的相关函数映射表,即上面的ErlNifFunc结构,load, reload, upgrade, unload是在NIF相关声明周期中调用的C语言的回调函数。

           在Erlang代码中,要用erlang:load_nif/2来加载NIF到当前进程的内存空间中。

     

    3,  数据交换

          这里涉及的一个主要问题是函数参数的传递和计算结果的返回:即函数调用时将Erlang传来的数据转换成C的,函数计算的结果返回时将C的数据转换成Erlang的。

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

           因此,所有的输入和输出都由统一的ERL_NIF_TERM类型表示,最后所有的NIF的C函数就可以统一用 Erlang代码

    1. ERL_NIF_TERM func(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])          

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

           对输入参数的处理,例如第一个输入到底是int的还是double的,这取决于程序逻辑的约定。虽然NIF也提供了一系列的enif_is_*函数进行判断,但主要靠程序员自己根据约定转换成C中具体的数据类型。Erlang传给C的参数的转换过程是通过一系列enif_get_*函数完成的。从版本R14A开始,Erlang能支持很多的C语言类型,具体可见官方文档。

          对输出(函数返回)的出来,要将C的数据类型转换成ERL_NIF_TERM,C返回给Erlang的数据的转换过程是通过一系列enif_make_*函数完成的,这组API生产的ERL_NIF_TERM数据最好视为只读的(想想erlang的不变的变量)。从NIF返回给erlang的这些ERL_NIF_TERM数据将由erlang节点管理并负责垃圾回收。

         所有ERL_NIF_TERM数据的属于某个ErlNifEnv数据,这些ERL_NIF_TERM数据的生命周期都与某个ErlNifEnv数据对象的生命周期有关。

    4,  简单例子

        我们用NIF实现一个求某个数N以内的素数的程序

    cprime.c程序

    1. #include <stdbool.h>  
    2. #include <math.h>  
    3. #include "erl_nif.h"  
    4.   
    5. static bool isPrime(int i)  
    6. {  
    7.         int j;  
    8.         int t = sqrt(i) + 1;  
    9.         for(j = 2; j <= t; ++j)  
    10.         {  
    11.                 if(i % j == 0)  
    12.                         return false;  
    13.         }  
    14.         return true;  
    15. }  
    16.   
    17. static ERL_NIF_TERM findPrime(ErlNifEnv *env, int argc, ERL_NIF_TERM argv[])  
    18. {  
    19.         int n;  
    20.         if(!enif_get_int(env, argv[0], &n))  
    21.                 return enif_make_badarg(env);  
    22.         else  
    23.         {  
    24.                 int i;  
    25.                 ERL_NIF_TERM res = enif_make_list(env, 0);  
    26.                 for(i = 2; i < n; ++i)  
    27.                 {  
    28.                         if(isPrime(i))  
    29.                                 res = enif_make_list_cell(env, enif_make_int(env, i), res);  
    30.                 }  
    31.                 return res;  
    32.         }  
    33. }  
    34.   
    35. static ErlNifFunc nif_funcs[] = {  
    36.         {"findPrime", 1, findPrime}  
    37. };  
    38.   
    39. ERL_NIF_INIT(prime, nif_funcs, NULL, NULL, NULL, NULL)  

       上面的C语言代码中,findPrime的参数由argv传入,argv[0]即是传入的参数N。


    erlang代码:

    1. -module(prime).  
    2. -export([load/0, findPrime/1]).  
    3.   
    4. load() ->  
    5.         erlang:load_nif("./cprime", 0).  
    6.   
    7. findPrime(N) ->  
    8.         io:format("this function is not defined!~n").  


    make之后运行结果:

    1. 1> prime:load().  
    2. ok  
    3. 2> prime:findPrime(50).  
    4. [47,43,41,37,31,29,23,19,17,13,11,7,5,3]  

    如例所示,erlang调用了C语言实现的findPrime函数打印除了50以内的所有素数。

  • 相关阅读:
    Qt计算器开发(三):执行效果及项目总结
    [HNOI2019]校园旅行
    How to fix nuget Unrecognized license type MIT when pack
    How to fix nuget Unrecognized license type MIT when pack
    git 通过 SublimeMerge 处理冲突
    git 通过 SublimeMerge 处理冲突
    git 上传当前分支
    git 上传当前分支
    gif 格式
    gif 格式
  • 原文地址:https://www.cnblogs.com/cobbliu/p/2388557.html
Copyright © 2011-2022 走看看