ErlangとPerlの速度比較
Erlangは並列処理が得意なので,それに関しては他の言語より圧倒的に速いのは確認したけれど,
わたしがよく使う通常の処理に関してもどの程度違うのか測定してみた.
一般的な処理に関しては,The Computer Language Shootout Benchmarks にたくさん結果があるので,そちらが参考になる.
言語としては,どちらも動的型付けで,コンパイラで内部表現に直してからインタープリタで実行,というあたりは一緒.
(ただしErlangはnativeコンパイラが別途ある)
ほぼ同じくらいの速度が出ても良さそうなところ.
Perlの代替を考えていたので,比較対象はPerl.
Language Shootout によれば,Erlang の方が多くのテストで2~3倍前後高速.nativeコンパイルすると,その差がさらに広がる感じ.
Erlang 5.5.4 と Perl v5.8.5.
SuSE Linux 9.2 (x86_32) でメモリ4G,Xeon 3G×2の環境で測定.
測定は5回ずつ行って,中央値を採用.
・" " を 0xfffff個 join
文字列の扱いの比較.Erlangは,StringとBinaryの2種類があるので,両方で測定した.
Stringは1文字あたり32bit環境で8バイト,64bit環境で16バイト使うので,あまり大きなデータには使えない.
Binaryは32bit環境で文字数+12~24バイト,64bit環境で文字数+24~48バイト.同じバイナリは共有される.
Binaryは扱いにくい部分があるので,使い分けが必要そう.
Perlは配列にしてつなげるより,.=でつなげた方が速かった.文字列が短いから配列管理のオーバーヘッドの方が大きいのかも? いずれの方法でもErlangの方が全般的に高速.
・"123あいう" の文字コードを 0xffff回変換
文字コードをUTF-8からShift_JISに変換する速度を測定.
Erlangはiconvを利用するモジュールが(非標準で)あり,それしか方法がないのでそれで測定. nativeコンパイルに関しては,iconvを利用するモジュール自体はnativeコンパイルなしで, ベンチを実行する側のみとした.
Perlは,Unicode::JapaneseとEncodeの両方で測定した.
どちらもモジュールのロードなどの時間は除いて,純粋に変換部分だけの時間を測定.
Erlangはsmpにすると極端に処理が遅くなってしまう.
Cのモジュールと通信してデータをやりとりする仕様だけれど, C側のモジュールがシングルスレッドで実行されて, そのモジュールとの通信を奪い合うのが原因?なのかなぁ.
C側のモジュールとのやりとりをマルチスレッドで実行させる方法があるのかもしれないけど, まだそこまで調べられてない状態. マニュアルをざっと眺めた限りでは無理そう...
どうも,外部プログラムは,Erlang内の1プロセスと同様の扱いになっていて, 漢字コードの変換のような,内部的な処理の高速化にCコードを使うのには向いていない感じ.
Erlangコードで漢字コード変換を書いて,それをnativeコンパイルした方が速いかもしれない.
Perl側は,Encodeが遅い.
Encode::from_toがinplaceでコンバートしてしまうために,直前に文字列コピーがあるのも影響しているのかも?
もう少し速くても良さそうだけども...
コードは以下のような感じ.
(UniJP側は,newする時間は含まない形で測定)
・正規表現でアクセスログからURL部分を0xfff回取得
正規表現で,アクセスログからURLの部分を取得するテスト.メソッドとHTTP/1.1の部分も含めて取得する.
Erlangは標準ライブラリにregexpがあるので,それを利用.
Perlの正規表現の圧勝.
というより,Erlangのregexpモジュールはかなり機能が貧弱なようで,普通には実装できなかった.
無理に正規表現で済ませようとした結果なので,速度が遅いと言うよりは機能不足かな.
その上,ソースを見てみたら,普通にErlangのコードで実装されていた. これでは言語自体に内蔵されているPerlにかなうはずが無い…
Perlなら,正規表現の中で()で一部分を指定して,それを$1で後から取得できる. でも,Erlangのregexpにはそのような機能がない.
結局以下のようなコードになってしまった.
この辺は,regexpじゃなくて,C言語的に実装しなければならないのかなぁ.
正規表現は便利なだけに,これはいまいち.
----
追記.
erl_driverのマニュアルを読むと,ロックレベルを変えることで,複数スレッドで同時に使えるようになる模様.
後日試してみよう・・・
一般的な処理に関しては,The Computer Language Shootout Benchmarks にたくさん結果があるので,そちらが参考になる.
言語としては,どちらも動的型付けで,コンパイラで内部表現に直してからインタープリタで実行,というあたりは一緒.
(ただしErlangはnativeコンパイラが別途ある)
ほぼ同じくらいの速度が出ても良さそうなところ.
Perlの代替を考えていたので,比較対象はPerl.
Language Shootout によれば,Erlang の方が多くのテストで2~3倍前後高速.nativeコンパイルすると,その差がさらに広がる感じ.
Erlang 5.5.4 と Perl v5.8.5.
SuSE Linux 9.2 (x86_32) でメモリ4G,Xeon 3G×2の環境で測定.
測定は5回ずつ行って,中央値を採用.
・" " を 0xfffff個 join
文字列の扱いの比較.Erlangは,StringとBinaryの2種類があるので,両方で測定した.
Stringは1文字あたり32bit環境で8バイト,64bit環境で16バイト使うので,あまり大きなデータには使えない.
Binaryは32bit環境で文字数+12~24バイト,64bit環境で文字数+24~48バイト.同じバイナリは共有される.
Binaryは扱いにくい部分があるので,使い分けが必要そう.
方法 | 言語 | 時間(sec) |
---|---|---|
文字列はString | Erlang (beam) | 0.312705 |
Erlang (native) | 0.118335 | |
Erlang (beam+smp) | 0.235028 | |
Erlang (native+smp) | 0.142893 | |
文字列はBinary | Erlang (beam) | 0.458387 |
Erlang (native) | 0.388640 | |
Erlang (beam+smp) | 0.415061 | |
Erlang (native+smp) | 0.343201 | |
配列にpushしてjoin | Perl | 0.741943 |
.=でつなげる | Perl | 0.538587 |
Perlは配列にしてつなげるより,.=でつなげた方が速かった.文字列が短いから配列管理のオーバーヘッドの方が大きいのかも? いずれの方法でもErlangの方が全般的に高速.
・"123あいう" の文字コードを 0xffff回変換
文字コードをUTF-8からShift_JISに変換する速度を測定.
Erlangはiconvを利用するモジュールが(非標準で)あり,それしか方法がないのでそれで測定. nativeコンパイルに関しては,iconvを利用するモジュール自体はnativeコンパイルなしで, ベンチを実行する側のみとした.
Perlは,Unicode::JapaneseとEncodeの両方で測定した.
どちらもモジュールのロードなどの時間は除いて,純粋に変換部分だけの時間を測定.
方法 | 言語 | 時間(sec) |
---|---|---|
iconv | Erlang (beam) | 0.604394 |
Erlang (native) | 0.612490 | |
Erlang (beam+smp) | 1.232886 | |
Erlang (native+smp) | 1.250170 | |
Unicode::Japanese | Perl | 0.646837 |
Encode | Perl | 1.403991 |
Erlangはsmpにすると極端に処理が遅くなってしまう.
Cのモジュールと通信してデータをやりとりする仕様だけれど, C側のモジュールがシングルスレッドで実行されて, そのモジュールとの通信を奪い合うのが原因?なのかなぁ.
C側のモジュールとのやりとりをマルチスレッドで実行させる方法があるのかもしれないけど, まだそこまで調べられてない状態. マニュアルをざっと眺めた限りでは無理そう...
どうも,外部プログラムは,Erlang内の1プロセスと同様の扱いになっていて, 漢字コードの変換のような,内部的な処理の高速化にCコードを使うのには向いていない感じ.
Erlangコードで漢字コード変換を書いて,それをnativeコンパイルした方が速いかもしれない.
Perl側は,Encodeが遅い.
Encode::from_toがinplaceでコンバートしてしまうために,直前に文字列コピーがあるのも影響しているのかも?
もう少し速くても良さそうだけども...
コードは以下のような感じ.
(UniJP側は,newする時間は含まない形で測定)
my $uj = new Unicode::Japanese; my $t0 = [gettimeofday]; for(my $i = 0; $i < 0xffff; $i++) { $uj->set("123あいう")->sjis; }
my $str = "123あいう"; for(my $i = 0; $i < 0xffff; $i++) { my $str2 = $str; Encode::from_to($str2, "UTF-8", "Shift_JIS"); }
・正規表現でアクセスログからURL部分を0xfff回取得
正規表現で,アクセスログからURLの部分を取得するテスト.メソッドとHTTP/1.1の部分も含めて取得する.
Erlangは標準ライブラリにregexpがあるので,それを利用.
方法 | 言語 | 時間(sec) |
---|---|---|
regexp | Erlang (beam) | 2.841569 |
Erlang (native) | 3.163096 | |
Erlang (beam+smp) | 2.474175 | |
Erlang (native+smp) | 2.693628 | |
正規表現 | Perl | 0.012415 |
Perlの正規表現の圧勝.
というより,Erlangのregexpモジュールはかなり機能が貧弱なようで,普通には実装できなかった.
無理に正規表現で済ませようとした結果なので,速度が遅いと言うよりは機能不足かな.
その上,ソースを見てみたら,普通にErlangのコードで実装されていた. これでは言語自体に内蔵されているPerlにかなうはずが無い…
Perlなら,正規表現の中で()で一部分を指定して,それを$1で後から取得できる. でも,Erlangのregexpにはそのような機能がない.
結局以下のようなコードになってしまった.
bench() -> {ok, Reg1} = regexp:parse( "^[^ ]+ [^ ]+ [^ ]+ \\[[^\\]]+\\] \""), {ok, Reg2} = regexp:parse( "^[^ ]+ [^ ]+ [^ ]+ \\[[^\\]]+\\] \"([^\"]+)\""), String = "12.34.123.12 - - [17/Apr/2007:23:53:13 +0900] \"GET /html/123.html HTTP/1.1\" 200 6980 \"http://ayaya.jp/html/123.html\" \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\"\n", Time = timer:tc(strregexp2, bench, [16#fff, Reg1, Reg2, String]), io:format("~w~n", [Time]). bench(0, _Reg1, _Reg2, _String) -> 0; bench(N, Reg1, Reg2, String) -> {match, _Start1, Len1} = regexp:match(String, Reg1), {match, _Start2, Len2} = regexp:match(String, Reg2), Match = string:substr(String, Len1 + 1, Len2 - Len1 - 1), bench(N - 1, Reg1, Reg2, String).
この辺は,regexpじゃなくて,C言語的に実装しなければならないのかなぁ.
正規表現は便利なだけに,これはいまいち.
----
追記.
erl_driverのマニュアルを読むと,ロックレベルを変えることで,複数スレッドで同時に使えるようになる模様.
後日試してみよう・・・
The comments to this entry are closed.
Comments