在本文里,我将说明Erlang为什么会与其它语言的运行时有所不同,还将说明它为什么总是用放弃吞吐量来换取更小的延时。
摘要: Erlang与其它语言的差异源于,双方有着不同的目标价值。我们常常发现Erlang在进程不多时表现不佳,反而在进程很多时表现优异,原因也在于此。
+A标识是为异步线程池定义线程数。驱动程序可以利用这个池子来阻塞某个操作,而调度者仍然可以继续做有用的工作。需要注意的是,这个线程池可以被文件驱动利用,以加速文件I/O,但不能加速网络I/O。
目前为止,我们讨论的都是OS内核层面的东西,当然也需要介绍一下Erlang(用户域)进程。当调用spawn时,系统会构建一个新进程,具体来说,是在用户域(userland)为它分配一个进程控制块。它的大小一般为600+字节,32位和64位系统下有所不同。系统将可运行的进程放入调度者的运行队列,当它们获得时间片时即可运行。
在详细介绍调度者之前,我想要先说明一下迁移的原理。进程偶尔会在调度者之间进行迁移,这是一个非常复杂的过程。它的目的是为了平衡所有调度者的负荷,保证所有核心能被有效地利用。但算法同时也会考虑工作是否多到要启动新的调度者,如果没有,它会保持调度器仍然处于关闭状态,这也意味着相应的核心可以进入节电模式并保持关闭。没错,Erlang会尽量省电。调度者们也可以在无工可开的情况下从别人那里「偷」工作。详情可参看[1]。 重点: 在R15中,调度者的启停是有延迟的。意思是说,Erlang/OTP认为调度者的启停很昂贵,只有它认为真正需要启停的时候才会去做这件事。比方说,现在某个调度者的情况是无工可开。但Erlang不会马上让它入睡,而是先让它继续跑一会,看看有没有新工作进来。如果恰好有,那就可以很快开工了,这样可以做到非常小的延时。但另一方面,这也意味着我们不能用top(1)和OS内核来衡量系统的效率,而必须用Erlang系统的内部调用来确定。许多人之所以认为R15比R14差,就是因为这个原因。 每个调度者运行着两类作业: 进程作业和端口作业。它们运行的时候也带优先级,类似于操作系统内核。我们可以将某个进程标识为高优先级、低优先级或其它优先级。进程作业执行的是进程。端口作业处理的是端口。Erlang中的“端口”是系统与外界通信的机制,文件、网络socket、通往其它程序的管道,都是端口。程序员可以通过加入“端口驱动”,以支持新类型的端口,不过那需要写C代码。调度者还可以轮询网络socket,从中读取数据。 普通进程和端口进程都有2000个运行次数限制,任何系统操作都会消耗这个限制, 包括循环调用,内置函数调用,垃圾回收,ets的读取,发送消息(接受者的邮箱信息越多,发送的成本越高).这真是无处不在. btw, erlang的正则库已经被改进,甚至用c写, 所以如果你长时间的进行正则运算, 对你不利并会占用消耗几个限制次数. port也是. 在ports上做io操作消耗限制,发送分布消息也是. 大量的限制消耗以保证任何进程都有一定的运行次数.由于以上的原因,我认为Erlang是真正执行抢占式多任务处理的语言之一,也是正确理解软实时概念的语言之一。同时,Erlang对时延看得比吞吐量更重,这在编程语言里也是不多见的。
更精确地来说,抢占,指的是调度者可以强制让某个任务停止执行。所有基于协作的语言和系统,包括Python、Node.js、LWT(Ocaml)等等,都无法做到这一点。更有趣的是,即使Go(golang.org)和Haskell(GHC)也不完全是抢占式的。Go只在通信时切换上下文,因此只需一个密集的循环即可独占某个核心。GHC则是在内存分配时切换(在Haskell程序中十分常见)。这些系统的问题在于,对核心的独占会影响整个系统的时延,大家可以想象一下在这些语言里执行数组操作的情形。
这就是软实时[3],即当定时失败时系统性能会急剧下降。比如说,在运行队列里共有100个进程。第一个进程执行的是数组操作,需要花费50ms。那么,如果是Go和Haskell/GHC[n3]的话,第2-100个进程就至少要50ms后才能完成。而如果换成Erlang,第一个任务只会得到2000个reduction,相当于1ms。当1ms过去后,系统就会把它放到队列尾部,换后面的任务来运行。所有的任务都能公平地分到属于自己的那一份时间。 本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务