2. The Seven Myths of Erlang Performance
2 The Seven Myths of Erlang Performance
有些事实看起来生活在远远超出自己最佳的日期之前,也许是因为“信息”在人与人之间传播速度比单个版本说明更快,例如,说主体递归调用变得更快。
这部分试图销毁已成为myths的旧真理(或半真理)。
2.1 Myth:尾递归函数比递归函数快得多
根据这个myth,使用一个尾递归函数来构建一个反向列表,然后调用lists:reverse/1
它比一个按照正确顺序构建列表的body-recursive函数更快; 原因在于主体递归函数比尾递归函数使用更多的内存。
在R12B之前,这在某种程度上是事实。在R7B之前更是如此。今天,不是那么多。主体递归函数通常使用与尾递归函数相同数量的内存。通常不可能预测尾递归或身体递归版本是否会更快。因此,使用让代码更清晰的提示(提示:通常是body-recursive版本)。
有关尾部和主体递归的更全面的讨论,请参阅Erlang's Tail Recursion is Not a Silver Bullet
。
注
尾部递归函数不需要在末尾逆序列表比尾部递归函数要快,而尾部递归函数也不会构造任何项(比如,一个将所有整数求和的函数一个列表)。
2.2 Myth:运算符“++”总是不好
++
操作符,有些不应存在。这可能与代码如下所示有关,这是扭转列表效率最低的方式:
DO NOT
naive_reverse([H|T]) ->
naive_reverse(T)++[H];
naive_reverse([]) ->
[].
当++
操作员复制其左操作数时,结果会被重复复制,从而导致二次复杂性。
但使用++
如下所示并不坏:
OK
naive_but_ok_reverse([H|T], Acc) ->
naive_but_ok_reverse(T, [H]++Acc
naive_but_ok_reverse([], Acc) ->
Acc.
每个列表元素只被复制一次。不
断增长的结果Acc
是对正确的操作++
操作符,并且它不
被复制。
有经验的Erlang程序员会这样写:
DO
vanilla_reverse([H|T], Acc) ->
vanilla_reverse(T, [H|Acc]
vanilla_reverse([], Acc) ->
Acc.
这样稍微高效一些,因为在这里你不会构建一个列表元素直接复制它。(或者,如果编译器不会自动重写[H]++Acc
,效率会更高[H|Acc]
。)
2.3 Myth:字符串处理慢
如果处理不当,字符串处理速度会变慢。在Erlang中,您需要更多地考虑如何使用字符串并选择适当的表示形式。如果使用正则表达式,请re
在STDLIB中使用模块,而不要使用过时的regexp
模块。
2.4 Myth:修复一个Dets文件非常慢
修复时间仍然与文件中记录的数量成正比,但过去修理过去要慢得多。Dets已被大量改写和改进。
2.5 Myth:BEAM是一个基于堆栈的字节码虚拟机(因此速度较慢)
BEAM是基于注册表的虚拟机。它有1024个虚拟寄存器,用于保存临时值和调用函数时传递参数。需要在函数调用期间生存的变量保存到堆栈。
BEAM是一个线程代码解释器。每条指令都是直接指向可执行C代码的指令,使得指令调度非常快速。
2.6 Myth:当不使用变量时,使用“_”加快程序速度
那曾经是真的,但是从R6B BEAM编译器可以看到一个变量没有被使用。
同样,源代码级别上的微不足道的转换(例如将case
函数顶层的语句转换为子句很少会对生成的代码产生影响)。
2.7 Myth:NIF总是加快你的计划
将Erlang代码重写到NIF以使其更快应该被看作是最后的手段。它只保证是危险的,但不能保证加快程序。
每个NIF电话会做太多的工作degrade responsiveness of the VM
。做得太少的工作可能意味着NIF中更快处理的获益被调用NIF和检查参数的开销所吞噬。
请务必在编写NIF之前阅读Long-running NIFs
。