关于nif

[size=x-large]一、NIF的误用问题[/size]

使用NIF是很危险的,一不小心它就会搞垮你的erlang VM,还会堵塞erlang调度器使VM进入假死状态。

平均每20个使用NIF的项目,就有19个滥用了NIF。参考:[url=http://jlouisramblings.blogspot.tw/2013/07/problematic-traits-in-erlang.html]NIF Abuse[/url]

[url=http://www.erlang.org/doc/man/erl_nif.html]NIF官方手册[/url]其实有所提示:
[quote]Avoid doing lengthy work in NIF calls as that may degrade the responsiveness of the VM. NIFs are called directly by the same scheduler thread that executed the calling Erlang code. The calling scheduler will thus be blocked from doing any other work until the NIF returns [/quote]

在[url=http://blog.yufeng.info/archives/953]例证NIF使用的误区[/url]一文中也有提醒使用NIF要小心。

官方手册建议我们得把每个NIF函数调用的时间控制在1ms以内。[url=http://www.erlang.org/doc/man/erl_nif.html]参考[/url]
[quote]It is hard to give an exact maximum amount of time that a native function is allowed to work, but as a rule of thumb a well behaving native function should return to its caller before a millisecond has passed.[/quote]


[size=x-large]二、一些基本原理和简单解释:调度器抢占与reductions计数[/size]

因为Erlang是[url=http://blog.yufeng.info/archives/2401]软实时系统[/url],其调度器有抢占其它erlang进程的能力。erlang给每个进程分配reductions(默认值是2000),对应普通Erlang函数,每执行一次函数调用会记一次reduction,调度器由此估算进程的执行时间。

参考[url=http://jlouisramblings.blogspot.tw/2013/01/how-erlang-does-scheduling.html]how Erlang does scheduling[/url]
[quote]Both processes and ports have a "reduction budget" of 2000 reductions. Any operation in the system costs reductions. This includes function calls in loops, calling built-in-functions (BIFs), garbage collecting heaps of that process[n1], storing/reading from ETS, sending messages (The size of the recipients mailbox counts, large mailboxes are more expensive to send to). This is quite pervasive, by the way. The Erlang regular expression library has been modified and instrumented even if it is written in C code. So when you have a long-running regular expression, you will be counted against it and preempted several times while it runs. Ports as well! Doing I/O on a port costs reductions, sending distributed messages has a cost, and so on. Much time has been spent to ensure that any kind of progress in the system has a reduction cost.[/quote]


[quote]This is also why one must beware of long-running NIFs. They do not per default preempt, nor do they bump the reduction counter. So they can introduce latency in your system.[/quote]

[size=large]2.1 实验:对erlang进程的reductions计数[/size]

通过实验可以验证基本上一个普通erlang函数的调用计为一次reduction。

测试代码如下:
-module(foo).
-compile(export_all).

sum(L) ->
sum(L, 0).

sum([], Acc) ->
Acc;
sum([H|Tail], Acc) ->
sum(Tail, H + Acc).


测试使用了bif函数process_info/2,它提供了查询erlang进程Pid当前reductions计数:
process_info(Pid, reductions).

我在一台2007年MacBook和一台台式机上测试,执行一次普通erlang函数调用的时间应该都小于1微秒(10^-6sec):
Erlang R16B01 (erts-5.10.2) [source] [64-bit] [smp:2:2] [async-threads:10] [kernel-poll:false] [systemtap]

Eshell V5.10.2 (abort with ^G)
1> timer:tc(foo, sum, [[]]).
{1,0}
2> timer:tc(foo, sum, [[1,2,3,4,5,6,7]]).
{1,28}
3> timer:tc(foo, sum, [[1,2,3,4,5,6,7]]).
{1,210}
4> timer:tc(foo, sum, [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]]).
{1,210}
5> L = lists:seq(1, 100).
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
23,24,25,26,27,28,29|...]
6> timer:tc(foo, sum, [L]).
{2,5050}
9> P = spawn(fun() -> receive Any -> go end, foo:sum(L), receive Any2 -> ok end end).
<0.47.0>
10> process_info(P, reductions).
{reductions,17}
12> P ! go.
go
13> process_info(P, reductions).
{reductions,225}
27> f().
28> L = lists:seq(1, 1000).
29> timer:tc(foo, sum, [L]).
{12,500500}

33> P = spawn(fun() -> receive Any -> go end, foo:sum(L), receive Any2 -> ok end end).
<0.76.0>
34> process_info(P, reductions).
{reductions,17}
35> P ! go.
go
36> process_info(P, reductions).
{reductions,1080}


顺便推算一下
foo:sum([1...1000])大概用掉了1000个reductions,耗时12微秒
估算100个reductions对应1微秒。(不同的处理器上结果差别可能很大,这个是很粗糙的推测,不适合做定量分析。)

一个reduction大致等于一次普通erlang函数调用。实际上由于不同函数执行时间不同,这种计算方法是很粗略的。


以上是对普通erlang函数(翻译成opcode由虚拟机执行的函数)的计算方法,对于IO操作和bif函数调用,reductions的计算又有不同。此外bif又有独特的trap机制保证其宿主进程(即调用进程)能随时被抢占。


[size=large]2.2 erlang调度原理:通过reductions给进程分配执行时间片[/size]

当一个erlang进程被调度执行时会赋给固定数量的reductions,默认是2000,这个进程就一直执行,直到:
[list=1]
[*]消耗(consume)掉所有reductions,
[*]该进程要等待接受消息而暂停。
[/list]
BTW:第二种情况下如果消息到达或超时的话该进程将重新进入调度,也就是排到运行队列等待被执行。这也是Actor模型的标准运行模式,[url=http://highscalability.com/blog/2013/3/18/beyond-threads-and-callbacks-application-architecture-pros-a.html]详见Beyond Threads And Callbacks - Application Architecture Pros And Cons 之 Actor Model (1 - 1)[/url]

在调度器的运行队列中等待的进程由round-robin算法决定执行,该算法给每个erlang进程分配一个固定大小的时间片(time slice),在erlang中就是一定数量的reductions,这些erlang进程都有相同的执行优先度。
参考:Characterizing the Scalability of Erlang VM on Many-core Processors
第3.3.1节


[size=medium]2.3 NIF函数调用与reductions计数[/size]

NIF函数调用与普通erlang函数调用有所不同,调用了NIF函数的erlang进程有可能会干扰其它erlang进程的公平调度,这是因为:
1.erlang进程调用NIF函数时要一口气执行完,期间是不会被打断的,也即正在执行NIF函数的erlang进程不能被抢占,不但不能被抢占,而且还堵塞了erlang的调度器进程;
2.一次NIF函数的调用如果只计一个reduction可能不公平,一个NIF函数调用可能要比一个普通的erlang函数调用耗时多了。

对第一点,目前版本的erlang没有什么好办法,我们只能修改程序逻辑以减少NIF函数的工作量,将一次大的计算分解成许多小任务,每个小任务由单独的NIF实现。但是这样做还要考虑第2点。因为即使是分解成小任务的NIF函数可能也是比较耗时的。

可以改写[url=https://github.com/davisp/sleepy]sleepy[/url]这个例子来证明一下。首先把休眠10秒的nif拆成10个休眠1秒的nif调用。


sleep() ->
lists:foreach(fun(_) -> slumber() end,
lists:seq(1, 10)).
slumber() ->
nif_error(?LINE).


static ERL_NIF_TERM
nifslumber(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
usleep(1000000); // 1 second = 1,000,000 microsecond
return enif_make_atom(env, "ok");
}


任务分解后你可能以为不会再有严重堵塞的情况发生了,但是实际测一下就会发现没什么不同,辛辛苦苦分解了任务好像都做了无用功。

仔细想一下调度所采用的时间片(即2000个reductions)就理解了,对于这种分解,reductions其实只是增加到10倍而已,如果原来一次nif函数调用消耗100个reductions,分解后也只是1000个reductions,远达不到2000。因此调用nif的进程由于没达到2000个reductions而不会被抢占。


另一个角度,通过调整nif函数slumber的执行时间,从1ns到100000ns,测试这些不同耗时的nif函数消耗的reductions,可以发现,尽管nif执行的时间不同,但是其消耗的erlang进程reductions却总是一样。这显然是不太合理的。

[size=medium]2.4 补救办法:让NIF函数“正确”计量进程reductions[/size]

这时候,一个NIF API就有了用武之地,enif_consume_timeslice是R16B新增加,它用于帮助NIF函数重新估算reductions。

enif_consume_timeslice函数表示当前调用NIF函数的进程消耗的时间片比例,我理解完整的一个时间片对应着2000 reductions(或者是1ms?)。
该函数的第二个参数是个百分比整数,取值区间在[1,100],例如如果是10,表示消耗了2000*10%=200个reductions。
另外,enif_consume_timeslice重新计算的timeslice是积累的:它计算的是从上次调用到本次调用这段时间内所消耗的timeslice。
其返回值是0/1(布尔值?)。表示当前调用enif_consume_timeslice时,对应的erlang进程所分配的时间片是否已耗完。如果耗完则nif调用应该赶紧退出调用,好让调度器进行抢占并安排其它进程执行。

实验验证:
static ERL_NIF_TERM
nifslumber(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
usleep(1000000); // one second
int isExhausted = enif_consume_timeslice(env, 100);
return enif_make_atom(env, "ok");
}


休眠时间一秒,远超1ms,因此可看成100%

执行修改后的sleepy会发现情况有所改善。

这种在nif实现过程中重新估算reductions的思路,与IO的调度思路是类似的。[quote="http://www.cnblogs.com/me-sa/archive/2013/01/08/2850910.html"]“IO也是公平调度的,把IO的处理量换算成reduction,算在宿主进程的时间片里面。”[/quote]

[size=x-large]三. 另一种解决办法:nif与OS线程[/size]

以上都是在NIF中执行耗时计算时,如何尽量避免对Elrang VM不良影响的思路。
另一种解决办法是干脆避免在Erlang进程中掉用NIF直接执行耗时的运算,改成通过OS线程执行这些计算,从而避免Erlang调度器堵塞。

在[url=http://jlouisramblings.blogspot.tw/2013/07/problematic-traits-in-erlang.html]NIF Abuse[/url]一文中作者给出了使用NIF的建议:
[quote]As a NIF you have to either respond asynchronously through an internal thread, or you have to cooperate and be ready to yield.[/quote]

大致思路就是将耗时的NIF计算放在一个单独的OS线程中执行,这个线程虽然不能接受Erlang时间发来的消息,但是可以发消息给Erlang进程。这样我们可以在Erlang进程中启动一个OS线程,并等待OS将计算结果以消息的方式发送过来。如前所述,等待消息的Erlang进程会被Erlang调度器抢占,也不会有堵塞调度器的问题。

3.1 例子
enif_thread_create(...

发送消息要注意的是新建一个env
ErlNifEnv *msgenv = enif_alloc_env();

enif_send(NULL, pid, msgenv, msg);

发送完要清除env
enif_clear_env(env);

另外,官方文档中强调,创建的OS线程要join,否则在NIF动态库unload的时候VM会崩溃。

相关[url=https://groups.google.com/forum/#!topic/erlang-programming/5Q1woHPlCSA]讨论[/url].
[quote]You do not have to join a created thread before the nif returns. If the
VM is crashing when a thread-creating nif returns then you are probably
doing something wrong.
"driver unloaded" correspond to "NIF library unloaded". That is quite
natural. Bad things will happen if a dynamic library is unloaded (driver
or nif) while existing threads execute code in that library. A NIF
library is only unloaded as the result of a module upgrade where the
old module gets purged OR if you replace a NIF library by making
repeated calls to erlang:load_nif from the same module.

A safe way to make sure that your thread is joined before the library is
unloaded is to create a resource object that acts as a handle to your
thread. The destructor of the resource can then do join.
Resource objects has a protection mechanism that postpone the unloading
of a nif library until the last resource object with a destructor in
that library is garbage collected. Maybe nif-created threads should have
a similar protection mechanism. Have to think about that...

/Sverker, Erlang/OTP[/quote]

一个[url=https://github.com/davisp/nif-examples]例子[/url]

注意:名词erlang进程和OS线程的区别。

[size=x-large]四、我的小结[/size]

感觉enif_consume_timeslice这个新引入的API还是没能彻底解决进程调度/抢占的问题,而且会带来新问题:
1. 重估NIF原生函数的reduction是个技术细活;
2. NIF原生函数实现中如果调用了第三方库的函数,这种情况就很难重估reductions;
3. 干扰了业务逻辑代码的正常实现逻辑,给开发增加复杂度。


启动OS线程异步计算的解决方案似乎不错,不过却失去了Erlang大并发进程的能力。

也许最终解决方案还是使用[url=http://blog.yufeng.info/archives/1438]Native Process[/url],但是Native Process的支持被一再推迟了,原先(2011)以为R15(2012)就会支持,今年(2013)的最新消息是要到[url=http://erlang.org/pipermail/erlang-questions/2013-April/073466.html]R18才会实现[/url],时间大概是后年(2015)。


[size=x-large]参考资料[/size]
[url=http://kth.diva-portal.org/smash/record.jsf?searchId=2&pid=diva2:392243]"Characterizing the Scalability of Erlang VM on Many-core Processors"[/url]

时间片(time slice)的名词解释
[quote]The period of time for which a process is allowed to run uninterrupted in a pre-emptive multitasking operating system. Generally you want your program to use it's entire time slice and not do anything that gives up control of the CPU while you have it.[/quote]
http://highscalability.com/blog/2013/3/18/beyond-threads-and-callbacks-application-architecture-pros-a.html
Windows下使用NIF扩展Erlang方法 Erlang中,NIF(Native Implemented Function)被用来扩展erlang的某些功能,一般用来实现一些erlang很难实现的,或者一些erlang实现效率不高的功能。NIF使用C开发,效率和C接近,比纯erlang实现要高。NIF会编译成动态库,直接动态加载到erlang进程空间调用,也是erlang扩展新方法最高效的做法。 阅读详情

相关推荐

Erlang中的NIF(Native Implemented Function)

为了提高Erlang的执行效率,它提供了一种称为NIF(Native Implemented Function)的机制,允许开发者使用C/C++编写原生代码来扩展Erlang的功能。为了提高Erlang的执行效率,它提供了一种称为NIF(Native Implemented Function)的机制,允许开发者使用C/C++编写原生代码来扩展Erlang的功能。NIFErlang的一种扩展机制,允许开发者使用C/C++编写原生代码,并将其作为Erlang函数的一部分进行调用。

AzProcessgroup的博客 292

rustler编写erlang nif

https://blog.csdn.net/ap114/article/details/118092301

erl_nif 扩展erlang的另外一种方法

erl_nif 扩展erlang的另外一种方法

Erlang 游戏后端性能优化总结

本人主要从事游戏后端开发,所以本文只从游戏开发角度分析Erlang使用中应注意的问题和优化点。  单节点还是多节点 Erlang节点之间的通信是透明的,节点内部和外部之间的调用一致。基于这样的特性,很多人会选用多节点,把各子系统(登陆节点,玩家节点,地图节点,全局节点等)分配到不同的节点中,以支持更多的在线玩家。这样做的出发点是好点是好的,但会引起一列表的问题:登陆、转场逻辑复杂,

blade2001的专栏 3039

Eigen库学习(七)Reductions,visitor和broadcasting

Reductions是什么? Eigen中reductions是将matrix或array作为输入,返回一个单一的标量值。最常见的就是sum,他将所有的系数求和结果返回。 #include <iostream> #include <Eigen/Dense> using namespace std; int main() { Eigen::Matrix2d mat; mat << 1, 2, 3, 4; cout << "Her

Fishfishfishfishcat 615

Erlang NIF的使用

Native Implemented Functions(NIF)可以用C来实现程序一些功能的扩展,一般用来实现一些用Erlang无法实现或者实现效率低的功能。 C语言编译生成的动态库(*.so)在Erlang调用C模块时动态加载到Erlang的进程空间中,调用NIF不用上下文的切换开销,但是安全性不是很高,因为NIF的crash会导致整个Erlang进程crash。 NIF的实现 先按官方文档上给的例子,初次实现一下NIF的使用: 1.创建niftest.c文件 #...

YOONGI 1279

Erlang NIF浅析

Erlang调用C代码时,NIF(Native Implemented Function)是比port driver更简单和有效的实现方式,尤其是编写同步程序中,NIF是非常适合Erlang 的。 1,  基本原理       NIF可以使我们可以用C实现相同的程序逻辑,但速度比用纯Erlang的快,跟C的速度很相近。       C语言编译生成的动态库(*.so)在Erlan

lxjames833539的专栏 1111

erlang nif 中文手册

前言 这是翻译erlang官方文档中的 erts-5.9.2的erl_nif部分。翻译完了。水平有限,我就把这个当作是我自己使用了,以后也会继续完善的。 erlang nif 中文手册 概括 功能 初始化 数据类型 接口-资源分配类 接口-线程操作类 接口-类型操作类 概括 NIF库包含了erlang模块

vihbc的专栏 1950

ripgrep 跨平台实用指南:3 条命令跑通命令行搜索

在三层嵌套的目录里找一个函数名,靠 IDE 点着找要多久?ripgrep 就是一个为这种场景准备的跨平台命令行搜索工具:它递归遍历目录匹配正则表达式,默认尊重 .gitignore,自动跳过隐藏文件和二进制文件。这篇教程带你从安装到按场景搜索再到配置,全部跑通一遍。 ## 🚀 30 秒上手:一条命令装遍三个系统 安装和第一条搜索命令放在同一节说清。三大系统的包管理器都有现成包,按你所在的系统

gitblog_00388的博客 768

SortedSet NIF 项目教程

SortedSet NIF 是一个由 Rust 实现的高效排序集合库,专为 Elixir 语言设计。该项目的主要目标是提供一个快速且高效的排序集合数据结构,适用于需要高性能排序和唯一性保证的应用场景。SortedSet NIF 的核心数据结构和算法通过 Rust 的 Native Implemented Function (NIF) 实现,确保了在 Elixir 环境中的高性能表现。 ### 主

gitblog_00006的博客 511

erl_nif_rustler_过程宏写法

原贴 https://blog.csdn.net/ap114/article/details/118092301 用rust 开发 erlang nif的正确做法

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

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

erlang nif test

erlang nif test demo

MJML NIF Elixir绑定指南

MJML NIF Elixir绑定指南 1. 项目目录结构及介绍 本项目基于GitHub上的仓库 adoptoposs/mjml_nif,致力于为Elixir提供MJML的Rust实现(Native Implemented Functions, NIF)绑定。下面是其主要的目录结构及各部分功能简介: lib: 包含了Elixir代码的核心库,这是使用MJML NIF的主要交互点。 mjml_...

gitblog_00077的博客 734

终极指南:如何在Blender中轻松处理Nif文件

如果你正在寻找一款强大的Blender插件来处理游戏模组开发中的Nif文件,那么PyNifly正是你需要的解决方案。这款专门为游戏模组制作设计的工具,能够帮助你在Blender中无缝导入和导出Nif文件,大大提升你的创作效率。 ## 什么是Nif文件?为什么需要专门工具? Nif文件是Bethesda游戏引擎中使用的3D模型文件格式,广泛应用于《上古卷轴》和《辐射》系列游戏中。对于想要进行游戏

gitblog_00242的博客 1429

erlang -nifs

NIFErlang OTP R13B03版引入的,在这一版中还只是一个实验特性,按照原计划,NIF在R14B版成为正式特性,相应的API也将在该版之后稳定下来。等不及了,先试试再说。 1. 基本原理 最大的好处是速度。Erlang程序的逻辑当然是用Erlang写的,速度上不能和C比。NIF使我们可以用C实现相同的程序逻辑, 而速度则是C的速度。 简单的说就是将C实现的程序编译成动态共享对象

yangzm的专栏 988

Erlang NIF 示例

会有快捷进入方式,或者全局搜索 VsDevCmd.bat脚本,一般会在·等环境下执行,如果安装正常,一般在。

iclod的专栏 254
上一篇: 自建riak帮助文档
下一篇: 小备忘录
iteye_13453
博客等级 码龄8年 4粉丝 96原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值