本节列出了一些需要注意的模块和bif,不仅仅是从性能的角度。
3.1 定时器模块
使用erlang:send_after/3和erlang:start_timer/3创建计时器要比使用STDLIB中的计时器模块提供的计时器高效得多。
计时器模块使用一个单独的进程来管理计时器。如果许多进程频繁地创建和取消计时器(特别是在使用SMP仿真器时),该进程很容易超载。
timer模块中的函数不管理定时器(如timer:tc/3或timer:sleep/1),不调用timer-server进程,因此是无害的。
3.2 list_to_atom/1
原子不会被垃圾收集。一旦一个原子被创造出来,它就永远不会被移除。如果达到原子数的限制约一百万(默认情况下为1,048,576),仿真器将终止。
因此,在一个持续运行的系统中,将任意输入字符串转换为原子可能是危险的。如果只允许某些定义良好的原子作为输入,则可以使用list_to_existing_atom/1来防止Dos攻击。(允许使用的所有原子都必须提前创建好,例如,只需在一个模块中使用所有原子并加载该模块即可。)
使用list_to_atom/1来构造一个atom,并将其传递给apply/3,这是非常昂贵的,不建议在时间要求严格的代码中使用:
apply(list_to_atom("some_prefix"++Var), foo, Args)
3.3 length/1
计算列表长度的时间与列表的长度成正比,而tuple_size/1、byte_size/1和bit_size/1都在常量时间内执行。
通常,不需要担心length/1的速度,因为它是用c高效实现的。在时间要求严格的代码中,如果输入列表可能很长,您可能希望避免使用它。
length/1的一些用法可以用match来代替。例如,以下代码:
foo(L) when length(L) >= 3 -> ...
可以写成:(作者的个人看法:这里的写法我感觉有点反人类,除非很确定 不然还是算了吧)
foo([_,_,_|_]=L) -> ...
一个细微的区别是,如果L是一个不合适的列表,length(L)将失败,而第二个代码片段中的模式接受一个不合适的列表。
3.4 setelement/3
setelement/3复制它所修改的元组。因此,使用setelement/3在循环中更新元组时,每次都会创建一个元组的新副本。
复制元组的规则有一个例外。如果编译器清楚地看到以破坏性方式更新元组会得到与复制元组相同的结果,则对setelement/3的调用将被替换为一个特殊的破坏性setelement指令。在下面的代码序列中,第一个setelement/3调用复制元组并修改第9个元素:
multiple_setelement(T0) -> T1 = setelement(9, T0, bar), T2 = setelement(7, T1, foobar), setelement(5, T2, new_value).
下面的两个setelement/3调用在适当的地方修改了元组。
为了应用优化,以下所有条件必须为true:
- 索引必须是整型文字,而不是变量或表达式。.
- 索引必须按降序给出。
- 在对setelement/3的调用之间必须没有对另一个函数的调用。
- 一个setelement/3调用返回的元组只能在后续调用setelement/3时使用。.
如果代码不能像multiple_setelement/1示例中那样结构化,那么修改一个大型元组中的多个元素的最佳方法是将元组转换为一个列表,修改列表,然后将它转换回一个元组。
3.5 size/1
size/1返回元组和二进制文件的大小。
使用BIFs tuple_size/1和byte_size/1为编译器和运行时系统提供了更多的优化机会。
另一个优点是,BIFs提供透析器(Dialyzer)更多的类型信息。
3.6 split_binary/2
使用match函数来分割二进制数据通常比调用split_binary/2函数更有效。此外,混合使用位语法匹配和split_binary/2可以防止某些位语法匹配的优化。
使用 <<Bin1:Num/binary,Bin2/binary>> = Bin,
不使用 {Bin1,Bin2} = split_binary(Bin, Num)