Hatena::Grouperlang

weekend erlang programmer

ここの更新は止まってしまいました。面倒なので全部kuenishiの日記に書くことにしました。
 | 

{2009, 5, 6}

erlang-C ffiを作ろうとしている 21:27 はてなブックマーク - erlang-C ffiを作ろうとしている - weekend erlang programmer erlang-C ffiを作ろうとしている - weekend erlang programmer のブックマークコメント

一昨日くらいに思いついた無駄コード第一弾に向けて、erlangのlinkedin driverについて調べています。で、メモ。

参考文献

プログラム的に

  • 前準備は?
    • erlang context:
    • erl_ddll:load_driver/1 でライブラリファイルをオープン。
      • このとき渡す名前が "xxx" だったとき、"xxx.so"というファイルがロードされる。多分、普通にdlopen。
      • さらに、この "xxx" に対してportをopenしなければならない。erlang:open_port/2
      • forkするみたい。pipeでやってるのかな。いずれにしろここがボトルネックか。。。
      • で、そのportの管理人をひとりspawnした。
      • portの管理人と、ライブラリが1:1で通信しているっぽい。ここがシリアライズされた待たされるのかな。
  • erl/C でのオブジェクトの渡し方は?
    • encode/1, decode/1は自分で書く。
    • encode(_) -> [byte]. % - バイトシーケンスを返さなければならない。
    • decode([Int]) -> Int .
    • ei使うと便利らしい
  • erl->C の呼び方は?
    • Port ! {self(), {command, encode(Msg)}}
    • DRIVER_INIT(x)で登録したErlDrvEntryに入っている関数ポインタが呼ばれる。
    • それぞれの関数ポインタに型が決められているが、static void (*)(ErlDrvData handle, char * buff, int bufflen )型だったりする
  • C->erl の戻り方は?
    • recieve {Port, {data, Data}} -> ok end
    • driver_outputという関数で返す。→ メモリ領域の解放はどうなるんだろう?
  • 後始末は?
    • Port ! {self(), close}
    • recieve {Port, closed} -> ok end
  • 結局
    • 各イベント毎に、driver_entry構造体の指定の位置に関数ポインタやらデータやらをツッコんでおけばいいみたい。
    • でも、処理の振り分けに関するコードは基本的に自分で全部書いておく必要があるみたい。

並列性能

↓のように、C言語FFI(リンクインドライバ)がボトルネックになりやすい仕組みになっているらしい(tutorialの冒頭の画像なんかがまさにそのイメージ)というのが、ずっと引っかかっていたので、その辺りを詳細に調べてみようと思う。

Erlangで,C言語のライブラリ(この場合はopenssl)を使う場合,リンクインドライバを使うのだが,リンクインドライバがそもそも同時に1つのプロセスからしか利用できないことが多い.

(file,inetのモジュールだけが例外で,smp対応のドライバになっている模様.)

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

みかログ: マルチコアでスケールしないErlang その2

少なくとも、"There may be multiple instances of a driver, each instance is connected to an Erlang port. Every port has a port owner process. Communication with the port is normally done through the port owner process."とあるので、最悪、ドライバを叩くプロセスを複数作ればいいかなぁと思っていた。それ作るだけでも割と使えるかも、と思ったけど、読み進めてみると

Previously, in the runtime system without SMP support, specific driver call-backs were always called from the same thread. This is not the case in the runtime system with SMP support. Regardless of locking scheme used, calls to driver call-backs may be made from different threads, e.g., two consecutive calls to exactly the same call-back for exactly the same port may be made from two different threads. This will for most drivers not be a problem, but it might. Drivers that depend on all call-backs being called in the same thread, have to be rewritten before being used in the runtime system with SMP support.

erl_driver

とあるので、SMP supportをきちんとつけておけば冒頭のボトルネック問題はR13Bでは解決されているっぽいかも(要確認)。で、それでもモジュールとしてスレッド等を構えておきたいのであれば、Threads, Mutexes, Condition variables, Read/Write locks, Thread specific dataなどが使えるみたい。というか、Cで書かれているところまで並行性の面倒は見切れません、という風に読める。

おまけ:MacOSでshared libraryを作るときの注意

MacOSだと、フツーは-dynamiclibなどとして動的ライブラリを作るんだけど、それだとダメだと注意されているので要注意(via Young Risk Taker)。

If you develop linked-in drivers (shared library) you need to link using "gcc" and the flags "-bundle -flat_namespace -undefined suppress". You also include "-fno-common" in CFLAGS when compiling. Use ".so" as the library suffix.

さらにおまけ

まだ何したらいいかよくわかってないときにErlang VM本体のソースも落としてきてみたんだけど、やっぱりワケワカランかった。こんなスゴいもの再発明してる場合じゃないね。

追記

みかログに載っているコードをコピってテストしてみたところ、10倍からは随分改善されたけど、マルチコアの優位性を使い切れていない。チューニングの問題なのかどうなのか。

> $ erl -noshell -smp disable -eval 'mesbench:test(),halt().'
{11242894,ok}
> $ erl -noshell -smp enable -eval 'mesbench:test(),halt().'
{13285326,ok}

SPARC x Solarisで使われてきたシステムだから、そっちだと性能が出るとか?うーむ。わからん。

 |