我们的代码仓库里面存在很多C语言的库,基于运行效率或工作量的考虑,我们不想
使用erlang重写相关库,我们想在erlang中像使用erlang库一样方便地使用C库,
vcall(erlang版)正是在这种背景下出现的。vcall(erlang版本)后面简称
vcall。
使用vcall时,我们只需要调用register接口批量
或单个注册我们要使用的函数,接下来我们就可以像使用erlang库提供的函数一样
使用被注册函数。vcall支持动态替换和卸载c库。
## 实现原理
vcall分为两部分:erlang部分(erl_vcall.beam,npc.beam);c部分(erl_vcall.dll
或 erl_vcall.so)。两部分之间使用连接符合nif相关约定。erlang部分对外(用户)
提供函数注册、动态替换c库、清理c库接口。c部分对erlang提供函数注册、函数函数
调用、动态卸载c库、动态加载c库、更新函数索引等接口。
函数注册(正常)过程:用户把函数相关信息传给erlang;erlang调用c注册接口;通过
动态库名、函数名、函数形参类型及个数,获取函数指针,并保存在全局变量中;erlang
将函数函数注册信息保存到ets表中(防止函数重复注册),并动态生成模块名、函数名、
形参个数和c函数一致的erlang代码;更新c中的全局变量的函数索引。
函数调用过程 :调用注册函数过程中生成的erlang函数(参考下面vcall使用示例);
erlang调用C对应调用接口;c解析参数并做参数的类型转换,以符合ffi的调用约定;
把参数、注册时保存的函数指针传递给ffi通用接口,然后把结果返回给erlang。
动态替换c库过程:调用相关接口,卸载原有c库并删除文件;拷贝新库到原有位置,重新
注册旧库中被注册的函数(注册信息从ets表中获取)。
## vcall使用示例
### 准备工作
erlang运行环境;vcall库编译生成对应文件,并放到相应目录下;被测试c库放到
erl_vcall.so(windows下erl_vcall.dll)同一目录下。
### 被测试动态库
被测试动态库名称为test(windows下test.dll,linux下test.so),源码如下
int add(int* a, int* b) {
return (*a + *b);
}
int* createInt(int a) {
int* p = (int*) malloc(sizeof(int));
*p = a;
return p;
}
void destroyInt(int* p) {
free(p);
}
int add2(int a, int b){
return a + b;
}
### 函数注册调用过程
调用npc:start_link([]).启动lpc模块(只需要执行一次)。
调用npc:register("test","int","add2","int,int",syn)注册函数。
接下来就可以使用 test:add(2,3)返回计算结果5。
其他函数的注册调用方法和上面的示例步骤一致。
## 性能测试
### 测试函数
int* createInt(int a);
int add2(int,int);
int add3(double,double);
int multiDouble(double,double,double,double,double,
double,double,double,double,double,double,double,
double,double,double,double,double,double,double,double);
int multiInt(int,int,int,int,int,int,int,int,int,int,
int,int,int,int,int,int,int,int,int,int);
### 测试方法
通过erlang库函数调用以上函数1000000次,求出函数调总耗时和平均耗时;通过调用空函数1000000次,求出nif层消耗的总时间和平均时间。
### 测试结果
#### 32位
<table>
<tr>
<td>函数</td>
<td>总耗时(微秒)</td>
<td>平均耗时(纳秒)</td>
</tr>
<tr>
<td>createInt</td>
<td>444226</td>
<td>444</td>
</tr>
<tr>
<td>add2</td>
<td>419352</td>
<td>419</td>
</tr>
<tr>
<td>add3</td>
<td>417791</td>
<td>417</td>
</tr>
<tr>
<td>multiInt</td>
<td>1041342</td>
<td>1041</td>
</tr>
<tr>
<td>multiDouble</td>
<td>1356512</td>
<td>1356</td>
</tr>
<tr>
<td>nif层</td>
<td>56113</td>
<td>56</td>
</tr>
</table>
#### 64位
<table>
<tr>
<td>函数</td>
<td>总耗时(微秒)</td>
<td>平均耗时(纳秒)</td>
</tr>
<tr>
<td>createInt</td>
<td>341248</td>
<td>341</td>
</tr>
<tr>
<td>add2</td>
<td>336733</td>
<td>336</td>
</tr>
<tr>
<td>add3</td>
<td>302804</td>
<td>302</td>
</tr>
<tr>
<td>multiInt</td>
<td>1035253</td>
<td>1035</td>
</tr>
<tr>
<td>multiDouble</td>
<td>1069095</td>
<td>1069</td>
</tr>
<tr>
<td>nif层</td>
<td>72439</td>
<td>72</td>
</tr>
</table>
#### 结论
1.函数调用64位系统稍快于32位,nif层64位耗时比32位长
2.函数调用的时长与参数类型关系不大,调用时长随参数个数的增加而增加
## 不足
目前vcall仅支持c基本数据类型,不支持复杂数据类型,不支持可变参数函数,
不支持函数重载。
ps:本文仅提供思路,源码并未开源恕不能提供。