【7.效率指南】 2.关于Erlang性能的七个传说

【效率指南】 关于Erlang性能的七个传说

有些真理似乎比它们的最佳期限还要长,也许是因为 "信息 "在人与人之间的传播速度比简单的发行说明要快,例如:非尾递归函数变快了

Erlang/OTP Version:24
System Documentation Version:12.0.2
导航:System Documentation - Efficiency Guide - The Seven Myths of Erlang Performance
前言:翻译水平低,可能存在错误和不妥之处,恳请读者批评指正。

1.传说:尾递归函数比非尾递归函数快

据传说,用一个尾递归函数构建一个倒序的list,然后通过lists:reverse/1进行反转,用一个非尾递归函数构建一个正序list更快;原因是非尾递归函数使用的内存比尾递归函数多。

在R12B之前,在某种程度上确实如此,在R7B之前更加如此。
今天,不是上面说的这样了。一个非尾递归函数使用的内存与尾递归函数相同。通常情况下,不能预测尾递归还是非尾递归哪个更快。因此使用代码结构清晰的版本。(提示:通常是非尾递归函数)
关于尾递归和非尾递归更深入的讨论,见:Erlang's Tail Recursion is Not a Silver Bullet.

注意
一个尾递归函数不需要在最后使用lists:reverse/1进行反转,那么它比非尾递归更快。

2.传说:运算符++总是低效率的

++运算符不应该有这样的坏名声。可能是像下面这样的代码,这是反转list最低效的方法:

%%低效写法
naive_reverse([H|T]) ->
    naive_reverse(T)++[H];
naive_reverse([]) ->
    [].

++运算符复制左边的操作数时,结果会被重复复制,导致二次复杂性。

但按照以下方式使用++就是不错的:

naive_but_ok_reverse([H|T], Acc) ->
	naive_but_ok_reverse(T, [H]++Acc);
naive_but_ok_reverse([], Acc) ->
	Acc.

list的每个元素只会复制一次。增长的结果Acc是++运算符右边的操作数,是不会复制它的。

经验丰富的Erlang程序员应该编写以下代码:

vanilla_reverse([H|T], Acc) ->
	vanilla_reverse(T, [H|Acc]);
vanilla_reverse([], Acc) ->
	Acc.

这样稍微高效一些,因为这里不用构建一个list,而是直接进行复制。

3.传说:Strings很慢

如果string使用不当,肯定会很慢。在erlang中,你需要多考虑下如何处理string,并选择一个合适的方式。如果你要使用正则表达式,请使用STDLIB中的re模块替换废弃的regexp模块。

4.传说:修复一个Dets文件非常慢

Dets已经过大量的重写和改进,如今快些了,但是修复时间仍然与Dets文件中record的数量成正比。

5.传说:BEAM是一个基于堆栈的字节码虚拟机(所以很慢)

BEAM是基于寄存器的虚拟机。它有1024个虚拟寄存器,用于保存临时值和函数调用时传递参数。需要在函数调用中存活的变量保存在堆栈中。
BEAM是一个线程代码解释器。每个指令都是直接指向可执行C代码的字,这使得指令调度非常快。

6.传说:当一个变量没使用时,用_替换它能提高程序速度

这在曾经是真实的。但是从R6B中,BEAM编译器可以看到没有使用变量。

7.传说:NIF总是能提高程序速度

重写Erlang代码为NIF来提高运行速度,这应该是最后的应对措施。它只会带来危险,而不会提高程序的运行速度。
在每个NIF中做太多工作,会降低VM的响应能力
做太少工作,可能意味着NIF中快速处理的收益被调用NIF和参数检查的开销所消耗。
在编写NIF前,务必阅读长期运行的NIF

翻译内容来自官方文档

posted @ 2021-09-11 16:14  AmazeBug  阅读(395)  评论(0)    收藏  举报