« July 2007 | Main | November 2007 »

Friday, September 28, 2007

マルチコアでスケールしないErlang その2

というわけでスケールしないことがわかるようなテストでやり直し.

ちなみに,測定は4コアの Intel Xeon E5335 @ 2.00GHz ×2の環境で行った.
データを共有しないような処理であれば,理論上は8倍の性能となる環境である.


まず,2つずつのペアのプロセスを作り,1つのプロセスがもう1つのプロセスへ多数メッセージを送るベンチ.
プロセスを同時100ペア起動し,メッセージを100000回ずつ送信・受信する.
それぞれのペア同士でしか通信を行わないので,SMP環境であればスケールしてよさそうに思えるところだが...

> erl -noshell -eval 'mesbench:test(),halt().'
{2886904,ok}
> erl -smp -noshell -eval 'mesbench:test(),halt().'
{29280698,ok}

10倍の差がついてしまう.


もう1つ.標準で付いてくるcryptoモジュールで暗号化をやるベンチ.
同時に100プロセスを起動し,それぞれが暗号化・復号を1000回ずつ行う.
ユーザ側のコードでは,データは一切共有しない.

> erl -noshell -eval 'crypttest:test(),halt().'
{2700277,ok}
> erl -smp -noshell -eval 'crypttest:test(),halt().'
{3896708,ok}

これもやはり遅くなってしまう.

Erlangで,C言語のライブラリ(この場合はopenssl)を使う場合,リンクインドライバを使うのだが,リンクインドライバがそもそも同時に1つのプロセスからしか利用できないことが多い.
(file,inetのモジュールだけが例外で,smp対応のドライバになっている模様.)

その上,このようなドライバの実装を見ると,いったん1つのプロセスにメッセージを集めて,そのプロセスがドライバと通信するような仕組みになっているので,絶対に速度が向上しない構造になってしまっている.


まぁ,そんなわけで,やっぱりSMP環境で性能が出ない感じ.
実際のいろいろな処理を行うコードでも,-smp を使うと速度が低下してしまって,使えないことが多いのではないかと思う.

やっぱり今後のバージョンアップに期待...


測定したソースは以下の通り.

・メッセージのテスト


-module(mesbench).
-compile(export_all).

test() ->
crypto:start(),
io:format("~p~n", [timer:tc(?MODULE, bench, [100, 100000])]).

bench(Proc, Count) ->
benchspawn(Proc, Count),
benchwait(Proc * 2).

benchspawn(0, _Count) ->
ok;
benchspawn(Proc, Count) ->
Pid = spawn(?MODULE, benchchild_recv, [self(), Count]),
spawn(?MODULE, benchchild_send, [self(), Count, Pid]),
benchspawn(Proc - 1, Count).

benchwait(0) ->
ok;
benchwait(Proc) ->
receive
ok -> ok
end,
benchwait(Proc - 1).

benchchild_recv(Parent, 0) ->
Parent ! ok;
benchchild_recv(Parent, Count) ->
receive
_ -> ok
end,
benchchild_recv(Parent, Count - 1).

benchchild_send(Parent, 0, _Receiver) ->
Parent ! ok;
benchchild_send(Parent, Count, Receiver) ->
Receiver ! {message, Count},
benchchild_send(Parent, Count - 1, Receiver).

・暗号化のテスト

-module(crypttest).
-compile(export_all).


test() ->
crypto:start(),
io:format("~p~n", [timer:tc(?MODULE, bench, [100, 1000])]).

bench(Proc, Count) ->
benchspawn(Proc, Count),
benchwait(Proc).

benchspawn(0, _Count) ->
ok;
benchspawn(Proc, Count) ->
spawn(?MODULE, benchchild, [self(), Count]),
benchspawn(Proc - 1, Count).

benchwait(0) ->
ok;
benchwait(Proc) ->
receive
ok -> ok
end,
benchwait(Proc - 1).

benchchild(Parent, 0) ->
Parent ! ok;
benchchild(Parent, Count) ->
Iv = <<"12345678901234567890">>,
Enc = encrypt(Iv, <<"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890">>),
Dec = decrypt(Iv, Enc),
benchchild(Parent, Count - 1).


% 暗号化関連

get_secretekey() ->
<<"1234567890">>.

encrypt(Iv, Data) ->
encrypt(get_secretekey(), Iv, Data).

decrypt(Iv, Data) ->
decrypt(get_secretekey(), Iv, Data).

encrypt(Key, Iv, Data) ->
{BinLen, PaddedData} = padding(Data),
concat_binary([BinLen, crypto:aes_cbc_128_encrypt(erlang:md5(Key), erlang:md5(Iv), PaddedData)]).

decrypt(Key, Iv, Data) ->
<> = Data,
PaddedData = crypto:aes_cbc_128_decrypt(erlang:md5(Key), erlang:md5(Iv), RestData),
unpadding(Len, PaddedData).

padding(Data) ->
Len = iolist_size(Data),
BinLen = <>,
{BinLen, [Data, list_to_binary(lists:duplicate((16 - (Len rem 16)) rem 16, "\0"))]}.

unpadding(Len, Data) ->
<> = Data,
BinData.


| | Comments (0) | TrackBack (0)

マルチコアでスケールしないErlangVM

マルチコア時代のErlang・・・のような書かれ方をしていることが多いけれども,
実際試してみるとそううまくスケールしてくれない.

Erlang の ring benchmark を Squeak Smalltalk で
の ring_bench を利用させていただいて,smp有無で測定をしてみると,こんな感じ.

smpを使うと,速くなるどころか約1/7の性能しか出なくなってしまう.

> erl -noshell -eval 'ring_bench:start(1000,10000), halt().' N = 1000, M = 10000; elapsed time = 1470 (1494) miliseconds

> erl -smp -noshell -eval 'ring_bench:start(1000,10000), halt().'
N = 1000, M = 10000; elapsed time = 9810 (9826) miliseconds

測定環境は,クアッドコアの Intel(R) Xeon(R) CPU E5335 @ 2.00GHz × 2,の環境.(合計8コア)

実際に利用していても,-smpを付けると遅くなるケースが目立つ.うーむ...

今後Erlang言語のバージョンアップで,改善するとよいんだけども...

----

追記.jijixiさんに言及いただいたので.

うーん、たしか ring benchmark ってシーケンシャルにメッセージを送っていくものだから、スケールしないのはむしろ当たり前なんじゃないのかな。

並列性が期待できない処理を smp にやらせても、逆にオーバーヘッドが増えて遅くなるだけというのは納得の行く結果だけど……なんかわし、勘違いしてるだろうか。

ええ,確かにその通り.(^^;

元々smp環境で処理が遅くなる問題があって,メッセージの速度をテストしてみたら
あまりにも遅かったので書いたという経緯だったのです.
でも,スケールしないことをテストするなら,リングを複数用意する等の方が適切ですね.

本家のMLにも投げてみたのですが,以下の返事でした.

I ran your benchmark on a machine similar to yours and got a time 6.7 times longer for the R11B-5 emulator with smp support compared to the emulator without smp support. With the, to be, R12B release I got a time 4.4 times longer.

ってわけで,R12Bでだいぶ良くなる模様です.

また,メッセージサイズを増やせば,差は縮まるとのこと.
確かに増やせば縮まりました.

そのほか,リンクインドライバのほとんど(fileとinet以外全部?)は
ドライバ単位でロックされてしまうので,その辺の影響も大きいかもしれない.

もう少し調べたらまた書いてみます...


| | Comments (0) | TrackBack (0)

« July 2007 | Main | November 2007 »