一般的な注意事項
まえおき
Erlang において、最低限注意しなければいけないことがまとまっていたので翻訳する。
元ドキュメント
もくじ
3.1 timer モジュール
3.2 list_to_atom/1
3.3 length/1
3.4 setelement/3
3.5 size/1
3.6 split_binary/2
3.7 -- 演算子
本文
このセクションでは、パフォーマンスの観点からだけではないが、注意すべきいくつかのモジュールとBIFを列挙する。
3.1 timer モジュール
erlang:send_after/3 と erlang:start_timer/3 を使用してタイマーを作成する方が、 STDLIB の timer モジュールが提供するタイマーを使用するよりもはるかに効率的である。 timer モジュールは、タイマーを管理するために別のプロセスを使用する。多くのプロセスがタイマーを頻繁に作成し、取り消すと(特に SMP エミュレータを使用している場合)、そのプロセスは簡単に過負荷になる。
タイマーを管理しないタイマーモジュールの機能( timer:tc/3 や timer:sleep/1 など)は、タイマーサーバープロセスを呼び出さないため、無害である。
3.2 list_to_atom/1
アトムはガベージコレクションされない。一度アトムが生成されると、それは決して削除されない。エミュレータは、アトムの数の制限(デフォルトで1,048,576)に達した場合に終了する。
したがって、任意の入力文字列をアトムに変換することは、連続して実行されるシステムでは危険である。特定の明確に定義されたアトムのみが入力として許可されている場合、 list_to_existing_atom/1 を使用して DoS 攻撃を防ぐことができる。(許可されているすべてのアトムは、以前に作成されたものでなければならない。たとえば、モジュール内のすべてのアトムを使用し、そのモジュールをロードするなど)。
次のように apply/3 に渡されるアトムを生成するために list_to_atom/1 を使用すると、非常に高価になり、タイムクリティカルなコードでは推奨されない。
apply(list_to_atom("some_prefix"++Var), foo, Args)
3.3 length/1
リストの長さを計算する時間は、 tuple_size/1 、 byte_size/1 、および bit_size/1 とは異なり、一定の時間内に実行されるリストの長さに比例する。
通常、 C で効率的に実装されているため、 length/1 の速度については心配する必要はない。タイムクリティカルなコードでは、入力リストが非常に長くなる可能性がある場合は回避することをお勧めする。
length/1 のいくつかの使用方法は、マッチングによって置き換えることができる。たとえば、次のコード:
foo(L) when length(L) >= 3 ->
...
次のように書き直すことができます:
foo([_,_,_|_]=L) ->
...
1つのわずかな違いは、 L が improper list である場合は length(L) が失敗し、2番目のコード例のパターンは improper list を受け入れるということだ。
3.4 setelement/3
setelement/3 はそれが変更するタプルをコピーする。したがって、 setelement/3 を使用してループ内のタプルを更新するたびに、そのタプルの新しいコピーが作成される。
タプルがコピーされるという規則に対する1つの例外がある。コンパイラがタプルを破壊的に更新すると、タプルがコピーされたのと同じ結果が得られることが明らかに分かると、 setelement/3 への呼び出しは特別な破壊的な setelement 命令に置き換えられる。次のコードシーケンスでは、最初の setelement/3 呼び出しがタプルをコピーし、9番目の要素を変更する。
multiple_setelement(T0) ->
T1 = setelement(9, T0, bar),
T2 = setelement(7, T1, foobar),
setelement(5, T2, new_value).
次の2つの setelement/3 呼び出しは、タプルを適切に修正する。
最適化を適用するには、以下の条件がすべて満たされている必要がある。
- インデックスは、変数または式ではなく、整数リテラルでなければならない。
- インデックスは降順で指定する必要がある。
-
setelement/3の呼び出しの間に別の関数を呼び出してはいけない。 - 1つの
setelement/3呼び出しから返されたタプルは、その後のsetelement/3の呼び出しでのみ使用する必要がある。
コードが multiple_setelement/1 の例のように構造化できない場合、大きなタプルの複数の要素を変更する最も良い方法は、タプルをリストに変換し、リストを変更し、タプルに変換することである。
3.5 size/1
size/1 は、タプルとバイナリの両方のサイズを返す。
R12B で導入された新しいBIFの tuple_size/1 と byte_size/1 を使用すると、コンパイラとランタイムシステムに最適化の機会が増える。もう一つの利点は、新しい BIF が Dialyzer があなたのプログラムでより多くのバグを見つけるのを助けることができることである。
3.6 split_binary/2
split_binary/2 関数を呼び出す代わりに、マッチングを使ってバイナリを分割する方が効率的である。さらに、ビットシンタックスマッチングと split_binary/2 を混在させることで、ビットシンタックスマッチングの最適化を防ぐことができる。
DO
<<Bin1:Num/binary,Bin2/binary>> = Bin,
DO NOT
{Bin1,Bin2} = split_binary(Bin, Num)
3.7 -- 演算子
-- 演算子は、そのオペランドの長さの積に比例する複雑性を有する。これは、そのオペランドの両方が長いリストである場合、演算子が非常に遅いことを意味する。
DO NOT
HugeList1 -- HugeList2
代わりに、 STDLIB の ordsets モジュールを使用する:
DO
HugeSet1 = ordsets:from_list(HugeList1),
HugeSet2 = ordsets:from_list(HugeList2),
ordsets:subtract(HugeSet1, HugeSet2)
明らかに、リストの元の順序が重要であれば、このコードは動かない。リストの順序を保持する必要がある場合は、次のようにする:
DO
Set = gb_sets:from_list(HugeList2),
[E || E <- HugeList1, not gb_sets:is_element(E, Set)]
Note
このコードは、リストに重複した要素が含まれている場合( HugeList2 内の要素が1つ出現すると、 HugeList1 内のすべての出現が削除される)、 -- と動作が異なる。
また、このコードは == 演算子を使用してリスト要素を比較し、 -- は =:= 演算子を使用する。その違いが重要な場合は、 gb_sets の代わりに sets を使用できるが、 sets:from_list/1 は長いリストの場合は gb_sets:from_list/1 よりもはるかに遅い。
-- 演算子を使用してリストから1つの要素を削除することは、パフォーマンスの問題ではない:
OK
HugeList1 -- [Element]