Table Of Contents

Previous topic

2. Erlangのパフォーマンスに関する8つの都市伝説

Next topic

4. バイナリの構築とマッチング

This Page

3. 警戒すべきモジュールと組み込み関数

ここでは、いくつか注意すべきモジュールと組み込み関数について説明していきます。ここで説明するのは、パフォーマンスの観点だけではなく、もっと広い視点で見ていきます。

3.1. regexpモジュール

regexpモジュールの中の正規表現関数はC言語ではなく、Erlangで書かれています。もともとは、アプリケーションの起動時に、コンフィグファイルの文法をチェックするなど、少ないデータに対して使用するのを意図して書かれました。

今後は、R13Aから導入された、reモジュールを代わりに使用してください。特に、大量のデータの処理など、時間に厳しい処理に関してはこちらを使用してください。

3.2. timerモジュール

timerモジュールで提供されているタイマーを使用するよりも、erlang:send_after/3および、erlang:start_timer/3を使用したほうが、効果的に書けるでしょう。timerモジュールは、タイマーを管理するのに、別のプロセスを使用しますが、多くのプロセスが頻繁にタイマーを作成したりキャンセルしたりしているうちに、間違って上書きしやすい、という問題があります。特に、SMPエミュレータを使用していると、この危険性が高まります。

timer:tc/3, timer:sleep/1などといった、timerモジュールの中のタイマーの管理をしない関数は、タイマーサーバプロセスを呼ぶことはないため、実害はありません。

3.3. list_to_atom/1

アトムはガーベジコレクタで回収されません。一度アトムが作成されると、削除されることはありません。もし、アトムが生成できる限界数(1048576)に達すると、エミュレータは終了します。

そのため、サーバのように継続して実行されるようなシステムにおいて、入力されたアルファベットで構成された文字列をアトムに変換していくようなコードは危険であるといえます。もしアトムとして使用可能なもののみが入力して入ってくる場合にのみlist_to_existing_atom/1を使用することで、サービス拒否攻撃から身を守ることができます。使用したいすべてのアトムはこの関数を呼ぶ前にすべて作成されていなければ、許容されません。例えば、必要なアトムがモジュール内で既に使用してあったり、もしくは使用したいアトムが定義されているモジュールをロードする必要があります。

apply/3 関数に渡すことで、 list_to_atom/1 を使用してアトムを構築することができます。:

apply(list_to_atom("some_prefix"++Var), foo, Args)

このコードは呼び出しのコストが高いため、時間の制約の厳しいコードでは使用しないほうがいいでしょう。

3.4. length/1

リストの長さを計算する処理時間は、リストの長さに比例します。一方、tuple_size/1, byte_size/1, bit_size/1は定数時間で実行することができます。

length/1はC言語を用いて効率的に実装されていますので、通常の場合には、length/1のスピードに関して心配する必要はありません。しかし、時間の制約の厳しいコードでは、入力されるリストが非常に長いということもありえるため、length/1を使用するのは避けたいと思うでしょう。

length/1を用いているケースのうち、パターンマッチに置き換えられるものもあります。例えば、以下のようなコードは置き換えることが可能です:

foo(L) when length(L) >= 3 ->
    ...

このコードは以下のようになります:

foo([_,_,_|_]=L) ->
    ...

Lが不適切なリストの時に失敗するという点だけが、length(L)とのわずかな違いになります。2めのコードが不適切なリスト [1] にも適用されてしまうからです。

[1]

(訳注) Erlang(を含む関数型言語)のリストは連結リスト(linked list)です。連結リストはセルという要素から構成されます。セルはデータを二つ持つことができ、それぞれhead/tailと呼ばれます。headに任意の値を入れ、tailに他のセルのheadへのポインタを入れていくと、一連のリストができます。擬似的には以下のような構造になっています:

セル = (head, tail)
空のセル = (nil, nil) # tailが次のセルへのポインタを持たない
リスト = (h1, (h2, (h3, ())))

リストの終端は、tailが次のセルへのポインタを持たない空のセルになります。Erlangでは表向きセルは出てきませんが、[H|T]というパターンマッチは、先頭のセルのheadとtailを取り出すことになります。上の例で言えば、:

H = h1
T = (h2, (h3, ()))

になります。

このように連結リストはtailで次のセルを指しているので、tailがセルへのポインタでなければ、連結リストとして使えません。例えば (h1, (h2, (h3, “string”))) のような構造になっていたら、連結リストとして処理しようとすると失敗します。最後のセルのtailがセルを指してないからです。このような連結リストが「不適切なリスト(improper list)」と呼ばれます。

3.5. setelement/3

setelement/3はタプルをコピーしてから変更します。そのため、ループの中でタプルをアップデートするのにsetelement/3を使用すると、毎回タプルの新しいコピーを作成してしまいます。

タプルがコピーされるというルールにはひとつだけ例外があります。コンパイラから見て、破壊的なアップデートを行っても、タプルのコピーを行っても、明らかに同じ結果になるということが分かれば、setelement/3の呼び出しは、特別な破壊的なsetelement命令へと置き換えられます。以下のようなコードがあったとします。:

multiple_setelement(T0) ->
    T1 = setelement(9, T0, bar),
    T2 = setelement(7, T1, foobar),
    setelement(5, T2, new_value).

最初のsetelement/3呼び出しはタプルをコピーして、9番目の要素を書き換えます。それに続く2つのsetelement/3の呼び出しではタプルをその場で変更します。

最適化が適用されるためには、以下の条件を満たす必要があります。

もし、長いタプルの複数の要素を変更したいが、上記のサンプルのmultiple_setelement/1のようにコードの構造を変更することができない場合には、一度リストに変換する方法が最適な方法になります。タプルをリストに変換して、リスト上で要素の変更を行い、最後にまたタプルに再変換します。

3.6. size/1

size/1はタプルとバイナリの両方のサイズを返すのに使用することができます。

R12Bから導入された、新しい組み込み関数のtuple_size/1, byte_size/1を使用すると、コンパイラやランタイムのシステムが最適化を行いやすくなります。もうひとつの利点としては、新しい組み込み関数を使用した方が、Dialyzerと連携することで、プログラムのバグを見つけやすくなるでしょう。

3.7. split_binary/2

通常の場合、split_binary/2関数を呼ぶ代わりに、マッチングを利用すると、効率よくバイナリデータを分割することができます。また、これに加えて、ビット文法のマッチングと、split_binary/2を混ぜて使用すると、ビット文法のマッチングの最適化が邪魔されます。

推奨:

<<Bin1:Num/binary,Bin2/binary>> = Bin,

非推奨:

{Bin1,Bin2} = split_binary(Bin, Num)

3.8. ‘–’演算子

‘–’演算子は、オペランドに来る要素の長さの積に比例して遅くなるということに注意してください。これはつまり、両方のリスト長ければ、非常に遅くなるということを意味しています。

非推奨:

HugeList1 -- HugeList2

これの代わりに、ordsetsモジュールを利用してください。

推奨:

HugeSet1 = ordsets:from_list(HugeList1),
HugeSet2 = ordsets:from_list(HugeList2),
ordsets:subtract(HugeSet1, HugeSet2)

これのコードは明らかに、入力時のリストの順序を保存したい場合には使用することができません。もし、リストの順番を保存したい場合には、以下のようにしてください。

推奨:

Set = gb_sets:from_list(HugeList2),
[E || E <- HugeList1, not gb_sets:is_element(E, Set)]

微妙な問題 1: このコードは、もしリスト内に重複した要素が格納されていた場合には、’–’を使ったコードとは異なった動作をします。HugeList2に一度でも登場すると、HugeList1に含まれるすべての要素が削除されることになります。

微妙な問題 2: このコードでは、 ‘==’ 演算子を用いてリストの要素の比較をしていますが、 ‘–’ 演算子は裏では ‘=:=’ を使用します。もしこの違いが重要であれば、gb_setsの代わりにsetsを用いることができます。ただし、長いリストに対して使用する場合、sets:from_list/1はgb_sets:from_list/1よりもはるかに遅いということに注意してください。

要素をひとつだけリストから削除する場合には、パフォーマンス上の問題はありません。

推奨:

HugeList1 -- [Element]

Copyright (c) 1991-2009 Ericsson AB