• 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以内的所有素数。

  • 相关阅读:
    mysqlslap
    Linux操作手册
    Linux操作手册
    Linux编程手册
    一篇文章搞懂CGlib动态代理
    (超详细!)彻底搞懂动态代理和静态代理
    (新手教学)IDEA快速搭建Spring
    十分钟彻底搞懂Java反射
    (面试题)如何之字形打印二维数组
    相同文件夹中其他jsp页面可以访问,但是个别访问不了
  • 原文地址:https://www.cnblogs.com/cobbliu/p/2388557.html
Copyright © 2020-2023  润新知