ラベル erlang の投稿を表示しています。 すべての投稿を表示
ラベル erlang の投稿を表示しています。 すべての投稿を表示

2014/02/26

Erlang VM(BEAM) スレッド構成

Erlang の実行環境である BEAM の動作を理解するため、BEAM のスレッド構成を調査しました。

BEAM は SMP(マルチコア) 環境と非 SMP 環境では動作が大きくことなります。SMP環境と非SMP環境に分けてスレッド構成を記載します。
調査対象の OTP のバージョンは R16B03-1です。

非SMP環境

Erlang Interactive Shell を起動する際に、オプションとして '-smp disable' を付与すると、CPUはSMPでも、BEAMとしては非SMPモードで起動できます。
'erl -smp disable' で起動すると、11個のスレッドが見つかりました。11スレッドの内訳は以下のようになります。
スレッド名関数名個数
Main Threadprocess_main1
Async Threadasync_main10

Main Thread
 BEAM byte-code を解釈し、実行するスレッドです。Erlang プロセスをスケジュールします。

Async Thread
 Erlang プロセスによるファイル操作を非同期に行います。プロセスが file モジュールを通じてファイルの読み書きや開閉を行うと、Main Thread に代わってAsync Threadがそれらの処理を請け負います。byte-code を解釈実行する Main Thread の動作を止めないために、処理を肩代わりしているのです。スレッドの起床は futex()システムコールでおこないます。
 Async Thread の個数は erl 起動時に '+A' オプションで変更できます。例えば、'erl +A 5' とすると、Async Thread は5個になります。ちなみに riak はデフォルトで64個起動します。

SMP環境

 SMP環境では些か構成が複雑になります。オプションなしで erl を起動すると、論理4core(物理2core)環境では19スレッドできました。内訳は以下になります。

スレッド名関数名個数
Main Threaderts_sys_main_thread1
Signal Handling Threadsignal_dispatcher_thread_func1
System Message Handling Threadsys_msg_dispatcher_func1
Async Threadasync_main10
Child Waiting Threadchild_waiter1
Scheduling Threadsched_thread_func4
Aux Threadaux_thread1

Main Thread
 非SMP環境とは異なり、Erlang プロセスの実行はしません。単にシグナルを受信して、pipe経由でSignal Handling Thread に通知するだけのスレッドです。select(0, NULL, NULL, NULL, NULL) で待ちぼうけです。

Signal Handling Thread
 シグナルハンドラ本体です。Main Threadが受信したシグナルに相当するハンドラを起動します。erl 起動時に '+B' オプションでシグナル受信時の挙動を変更できます。例えば 'erl +B i' でブレークシグナルを無視するようになります。

System Message Handling Thread
 システムメッセージのハンドラです。システムメッセージは、トレース情報の出力やプロセスの再開・中断等をリクエストする特殊なメッセージです。詳しくは sys module のドキュメントを参照ください。

Async Thread
 非SMP環境と同様の非同期I/Oスレッドです。

Child Waiting Thread
 「OTP-3906 : Solaris で子スレッドが大量に終了した際、 SIGCHLD がうまく伝わらない問題」を修正するため、子スレッドの終了を waitpid() で待ち受けます。

Scheduling Thread
 process_main() を実行し、 byte-code 解釈実行、プロセススケジューリングを行います。デフォルトでは論理コアと同じ数だけ生成されます。'+S' オプションでスレッド数を調整できます。他の Scheduling Thread と比較して負荷が偏らないようにバランシングとプロセスマイグレーションも行います。

Aux Thread
 若干時間のかかる処理を受け持つ補助的なスレッドです。メモリアロケーションやGCの情報を取得する際等に、Scheduling Thread から処理をオフロードされます。例えば、'elrang:statistics(garbage_collection)' でのGC統計情報取得は、aux_threadで行われます。

通常 erl 起動時に作られるスレッドは以上ですが、他にも、NIF や driver 関係のスレッドがあります。

まとめ

 ・Erlang VM(BEAM)のスレッド構成はSMP/非SMPで大きく異なる
 ・Scheduling Thread の動作を阻害しないために、一部処理が他スレッドにオフロードされる

参考文献

Erlang User's Guide: erl
Erlang/OTP のソースコード (otp/erts)

2014/01/16

Erlang/OTP crypto モジュールエラー on Fedora 19

Fedora 19 環境で自分でインストールした Erlang/OTP を使って、rebar を利用したところ、以下のようなエラーが出ました。

Uncaught error in rebar_core: {'EXIT',
                              {undef,
                                  [{crypto,start,[]},
                                   {rebar_core,run,1},
                                   {rebar,main,1},
                                   {escript,run,2},
                                   {escript,start,1},
                                   {init,start_it,1},
                                   {init,start_em,1}]}}

どうやら、crypto モジュールをstart()した時に問題が発生したようです。
調べてみると、bugzilla にエントリが在りました。

Bug 1023017 - Restore ECC support in Erlang's crypto library

さらに詳しく調べると、ruby でも問題になっていました。

backport r41808(openssl build issue on fedora)

原因は、OpenSSL で OPENSSL_NO_EC が define されていないにも関わらず、OPENSSL_NO_EC2M が define されている環境においてビルドすると、定義されていないシンボル EC_GROUP_new_curve_GF2m (ガロア体GF(2m)上の楕円曲線暗号を扱う関数)を利用してしまうことにあるようです。
解決策として、Erlang/OTP の lib/crypto/c_src/crypto.c の EC_GROUP_new_curve_GF2m を利用している if 節を "ifndef OPENSSL_NO_EC2M" でくくってやれば問題ないことを確認しました。
さて、パッチを送るかと思っていると、既に今より16日前に解決済みであることを知りました。

対策パッチ:
crypto: selective support for GF2m curves

関連Pull Request:
more EC curves

上記のパッチを当てれば、問題は解決されます。

2014/01/08

BEAM(Erlang VM) 参考資料まとめ

はじめに

Erlang/OTP で開発したアプリケーションは、通常 BEAM (Erlang VM)と呼ばれる仮想マシン上で動作させます。BEAMに関する資料は、今のところ世の中にあまり多くないようです。BEAMの情報が得やすくなるよう、ここにまとめておきます。

BEAM(Erlang VM) 参考資料

Hitchhiker’s Tour of the BEAM

Erlang Solutions Ltd. の Robert Virding 氏による BEAM の概要解説。Scheduler, Memory 管理, GC, Async Threads について小気味よくまとまっています。

The evolution of the Erlang VM

同氏による Erlang VM の歴史解説。Erlang VM ごく初期のProlog Interpreter や JAM(Joe's Abstract Machine) からBEAMに至るまでの経緯が書かれています。

Erlang Engine Tuning, Know Your Engine – Part II: the BEAM

ERTS本執筆中のErik (Happi) Stenman氏による BEAM解説。Erlang からコンパイルされたBEAM コードがどう解釈されて動作するのか説明されています。

Inside the Erlang VM

Ericsson の Ludin 氏によるErlang VM のについての解説。 主に Scheduler の実装と SMP 対応について書かれています。

How Erlang does scheduling

Erlang VM の Scheduling 方法についての平易な解説。プロセス優先度設定や、プロセスコンテキストスイッチの契機、他の言語(実行環境)との差異について書かれています。


2013/11/27

Erlang/OTP の systemtap トレース機能の使い方

はじめに

Erlang/OTP には systemtap と連携したトレース機能が備わっています。Erlang/OTP で systemtap 連携を有効にすると、systemtap から Erlang のトレースポイントを扱うことができます。トレースポイントを使うことで、BEAM 仮想マシンの挙動をより正確に把握したり、性能解析しやすくなります。

使い方

Erlang/OTP の ビルド

systemtap 連携を使うには、Erlang/OTP を configure --with-dynamic-trace=systemtap でビルドする必要があります。
# git clone git://github.com/erlang/otp.git
# cd otp
# ./otp_build autoconf
# ./configure --with-dynamic-trace=systemtap
# make

Erlang Shell を起動して [systemtap] と表示されていれば、systemtap が有効になっていることが確認できます。
# ./bin/erl
Erlang R16B03 (erts-5.10.4) [source-fb0006c] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [systemtap]

Eshell V5.10.4  (abort with ^G)

stap スクリプト起動方法

まず、stap -L コマンドで トレースポイントを確認しましょう。予め、beam バイナリファイルへの PATH を通しておいてください。
# PATH=/home/eiichi/git/otp/bin/x86_64-unknown-linux-gnu/:$PATH stap -L 'process("beam.smp").mark("*")' 
process("beam.smp").mark("aio_pool__add") $arg1:long $arg2:long
process("beam.smp").mark("aio_pool__get") $arg1:long $arg2:long
process("beam.smp").mark("bif__entry") $arg1:long $arg2:long
process("beam.smp").mark("bif__return") $arg1:long $arg2:long
process("beam.smp").mark("copy__object") $arg1:long $arg2:long
process("beam.smp").mark("copy__struct") $arg1:long
process("beam.smp").mark("dist__monitor") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long
process("beam.smp").mark("dist__output") $arg1:long $arg2:long $arg3:long $arg4:long
process("beam.smp").mark("dist__outputv") $arg1:long $arg2:long $arg3:long $arg4:long
process("beam.smp").mark("dist__port_busy") $arg1:long $arg2:long $arg3:long $arg4:long
...

約60個のトレースポイント(user-probe 系を除く)が確認できます。Linux Kernel のトレースポイントは 約 1200個、Qemu のトレースポイントが 約 900個なのと比較すると、若干少ないですね。

これらトレースポイントのうち、gc_major_start を例にトレースポイントの使い方を説明します。

まず、下記のような stap スクリプトを用意しましょう。(otp/lib/runtime_tools/examples より抜粋)
# cat garbage-collection.systemtap 
probe process("beam.smp").mark("gc_major-start")
{
    printf("GC major start pid %s need %d words\n", user_string($arg1), $arg2);
}

systemtap を有効にした Erlang Shell を起動し、下記のコマンドでsystemtap スクリプトを起動します。
# PATH=/home/eiichi/git/otp/bin/x86_64-unknown-linux-gnu/:$PATH stap garbage-collection.systemtap                    
Erlang Shell で適当に > "aaaaaaaaaa". などとコマンドを入力すると、stap スクリプトで下記のような出力が得られます。
GC major start pid <0 .33.0=""> need 9 words

GCの他にも、Message の送受信、プロセスの spawn、スケジュールなど、興味深く有用なトレースポイントがありあますので、お試しください。
systemtap スクリプトの例は lib/runtime_tools/example にあります。

dyntrace(user-probe)

Erlang/OTP で systemtap 連携を有効にすると、dyntrace モジュールを使って、 Erlang コードから動的にsystemtap スクリプトに情報を出力することができます。
Erlang/OTP に同伴されている、lib/runtime_tools/example/user-probe.systemtap を例にとって dyntrace モジュールの使いかたを説明します。

まず、user-probe.systemtap は beam 向けになっていますので、beam.smp 向けに直しましょう。user-probe.systemtap を開き、process('beam') となっているところを process('beam.smp') に書き換えます。
# vim user-probe.systemtap

書き換えたら、Erlang Shell を起動し、user-probe.systemtap を起動します。(beam に PATHを通しておいてください。)
# stap user-proeb.systemtap

この状態で、Erlang Shell にて、以下のように入力します。
2> dyntrace:p(1, 2, 3, 4, "a", "b", "c").
true
3> dyntrace:put_tag("test").
undefined
4> dyntrace:p(1, 2, 3, 4, "a", "b", "c").
true

すると、stap スクリプトでは、以下のような出力が得られます。
<0 .33.0="">  1 2 3 4 'a' 'b' 'c' 'c'
<0 .33.0=""> test 1 2 3 4 'a' 'b' 'c' 'c'

dyntrace:p/nで各種情報を出力します。dyntrace:put_tag/1 でトレース出力のプレフィックスを設定することができます。
詳しくはErlang User's Guide: dyntrace をご覧ください。

参考文献

Dtrace and Erlang: a new beginning
Erlang User's Guide: Systemtap and Erlang/OTP
Erlang User's Guide: dyntrace
Systemtap and Erlang: a tutorial
runtime_tools/src/dyntrace.erl

2010/11/12

Erlang + Websocket でCanvas共有

拙作のerlang_websocket_serverを用いて、Canvas共有をErlangに移植しました。
非同期メッセージパッシングというパラダイムのおかげで簡単に書けます。

こちらです!どんどん落書きしてください!
http://etsukata.com/erl/cvws.html
ChromeおよびSafariで動作確認しています。複数のウインドウを立ち上げて描いてみてください。

クライアントサイドのコードはNode.js版と全く一緒です。
サーバーサイドのコードを以下に掲載します。

githubでお読みになると、横スクロールがなくて見やすいです。
https://github.com/Etsukata/erlang_websocket_server/blob/master/example/canvas_sharing_handler.erl

-module(canvas_sharing_handler).
-import(websocket_server, [unicast/2, broadcast/2, sendall/1]).
-compile(export_all).

go() -> websocket_server:start("etsukata.com", 9000, ?MODULE, canvas_sharing_handler, [[],[]]).

canvas_sharing_handler(IDList, PointList) ->
  receive
    {open, ConnectionID} ->
      broadcast("@NC:" ++ ConnectionID, ConnectionID),
      unicast("@ID:" ++ ConnectionID, ConnectionID),
      StrIDList = lists:foldr(fun(X, Xs) -> X ++ "," ++ Xs end, "", IDList),
      Str = case StrIDList of
              []   -> [];
              _Any -> lists:sublist(StrIDList, length(StrIDList) -1)
            end,
      unicast("@PT:" ++ Str, ConnectionID),
      lists:foreach(fun(X) -> unicast(X, ConnectionID) end, lists:reverse(PointList)),
      canvas_sharing_handler([ConnectionID|IDList], PointList);
    {message, Data, ConnectionID} ->
      case lists:member($@, Data) of
        true ->
          case string:substr(Data, 1, 3) of
            "@CU" ->
              broadcast(Data, ConnectionID),
              canvas_sharing_handler(IDList, []);
            _Any ->
              canvas_sharing_handler(IDList, PointList)
          end;
        false ->
          broadcast(Data, ConnectionID),
          canvas_sharing_handler(IDList, [Data|PointList])
      end;
    {closed, ConnectionID} ->
      broadcast("@CL:" ++ ConnectionID, ConnectionID),
      canvas_sharing_handler(lists:delete(ConnectionID,IDList), PointList);
    Any ->
      io:format("Any:~w~n", [Any]),    
      canvas_sharing_handler(IDList, PointList)
  end.
Node.js版のソースコードとほとんど長さは変わっていません。
Node.jsのスピード感のある開発も楽しいです。しかしErlangのメッセージパッシングで考えたほうがほうが単純に物事を捉えることができ、これもまた楽しいのです。僕はNode.jsとErlang、どっちも好きです!

2010/11/11

ErlangでWebSocket Server実装を書きました

こちらで公開しています
https://github.com/Etsukata/erlang_websocket_server
この記事はGithubに公開している情報を日本語で若干加筆して提供いたします。


動機
以前Node.jsのWebSocket Server実装であるnode-websocket-serverを用いてCanvas共有アプリをつくりました。そのとき手軽でスピード感のある開発に快感を覚えたので、ネットワークとの親和性のが非常に高く、今注目を集めているErlangではできないものかと思い、作りました。
Dave Bryson氏による実装であるerlang_websocketを主に参考にさせて頂きました。氏の実装では一対一の相互通信を行うものであったので、それを拡張し、特定のクライアントにデータを送信したり、マルチキャストしたりできるようにしました。
なにより、node-websocket-serverのように手軽にWebSocketを用いたアプリをつくれるようにするのが目的です。

Process Design Pattern
Erlangでシステムを構築するときはどのようなProcess Design Patternを採用するかが鍵になります。当ライブラリでは以下のような構造になっています。



Socket Receiverがクライアントからデータを受け取り、順々に右のプロセスに渡していきます。ReceiverはWebSocket Frameから生のデータを取り出しHandlerに渡します。Handlerはそれを加工しSenderに渡します。SenderはWebSocket Frameをつくり、各クライアントとつながっているSocket Senderに対しユニキャストまたはマルチキャストでデータを配信します。Socket Senderは受け取ったものをクライアントに送信します。
と、いった感じです。
ユーザーはHandlerの振る舞いについてのみ記述すればいいようになっています。

サンプル
エコーサーバーを書く場合は以下で十分です。

-module(echo_handler).
-compile(export_all).
-import(websocket_server, [unicast/2]).
go() ->
   websocket_server:start("localhost", 9000, ?MODULE, default_echo_handler, []).
default_echo_handler() ->
  receive
    {message, Data, ConnectionID} ->
      unicast(Data, ConnectionID),
      default_echo_handler();
    _Any -> default_echo_handler()
  end.

ドキュメント
こちらにあります。
http://etsukata.com/erl/docs/


今後
これからはどのような機能を盛りこんでいくかを決めるために、いくつかこのライブラリを用いたWebSocketアプリをつくっていくつもりです。経験を積んで、何が必要で重要なのか、何が要らない物なのかを見定めたいと思っています。

参考URL
Comet is dead long live websockets
http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html
davebryson / erlang_websocket
https://github.com/davebryson/erlang_websocket
MiCHiLU / erlang_websocket
https://github.com/MiCHiLU/erlang_websocket