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