Gambit Scheme その3: 拡張機能: 宣言文により高速化、IOポート、Serializationによる並列処理
「Marc Feeley教授によるGambit Schemeの実装」の続き。
参照: http://www.iro.umontreal.ca/~gambit/Gambit-inside-out.pdf (23mのPDFファイル)
宣言文
宣言によりR5RSに準じたコンパイラにはできない最適化を許す。common lispにあるような機能だと思う。
- (declare (standard-bindings)) carなどの標準の関数などをインラインさせる。
- (block) ファイル内のグローバル変数はそこ以外で変更されないという約束
- (fixnum) (flonum) 演算の際、変数のタイプを指定する?
- (not safe) タイプチェックを省く
- (not proper-tail-calls) tail callしなくてもいい
など。R5RSのピュアなschemeに宣言を加えることにより5倍速くなる過程を見せる表がある(27ページ)。
Namespaces
モジュール機能はこのネームスペースを使って実装しているみたい。
スレッド
- green thread: OSのでなくGVM内のスレッド。
つまりマルチコアはそのままでは使いきれないってことか?
- Preemptive scheduler with priorities
グリーンでpreemptiveってのは凄くない? cooperativeなのが多いような気がする。
- 軽量、スケーラブル: スレッド毎のオーバヘッドは324バイト。1GBのメモリで数百万のスレッドを走らせられる。
- 速い: 0.0005秒でスレッド生成
詳細はp36前後を参照
Mailboxes
スレッド間のメッセージ機能。erlangみたいなものかな?
IOポート階層
ファイル、ディレクトリ、プロセス、TCPクライアント、サーバ、文字列、ベクトルなどがポートとして使える。このIPポートはR5RSのモデルを拡張したもので、オブジェクト指向的なクラス階層からなる。ポートクラスがこのような階層を構成する:
byte port
Serialization
ここからLispのマジックが始まる…
- オブジェクトをバイトベクトルに変換することができる
- closure(クロージャ)、continuation(継続)、cycles(?)もシリアライズ可能!
- Gambitに基いた分散処理系、Termiteはこの機能を使っている (ストレステスト済み)
- 異種ホスト(CPU,OS,32/64ビット、エンディアン)間でデータ(コード)を共有可能
オブジェクトをjsonのようなテキストフォーマットにしてそれをパースするという世俗的なものではない。オブジェクトをバイナリとして送ることができる。Lispではコードもデータと言うが、まさにその通り、動いているコードを凍らせて別のマシンに送ることができるようだ。
これは凄い。昔ニュースグループの議論の中で「関数(クロージャ)をプロセス外に送り出すことは不可能」との主張に対して「Lispなら可能」だする回答を見て、そんなことをできるのかと感心したが、ここで始めてその実例を見た。
Serializationによる並列処理
(let ((serv-sock (open-tcp-server "*:5000")))
(print "listening at *:5000\n")
(let loop () ;; クライアントからのリクエストを待ち、答えを送り返す。
(let* ((conn (read serv-sock))
;; バイトベクトルをポートから読む。
(bytes (read conn))
;; バイトベクトルを関数に変換!
(thunk (u8vector->object bytes))
;; 関数を実行
(result (thunk))
;; ベクトルにして送り返す。
(response (object->u8vector result)))
;; 結果は整数じゃないのか?
;; (print "responding with " result "\n")
(write response conn)
(close-port conn)
(loop))))
;; 上のサーバに対するクライアントAPI
;; 指定のサーバ上(on)で与えた作業(thunk)をして結果を送り返す。
(define (on address thunk)
(thread-start!
(make-thread
(lambda ()
(let ((conn (open-tcp-client address)))
(write (object->u8vector thunk) conn)
(force-output conn)
(let ((result (u8vector->object (read conn))))
(close-port conn)
;; (print "got " (object->string result))
result))))))(define (test n)
;; この関数のクロージャを送り出す。
(define (f n)
(if (< n 2) 1 (* n (f (- n 1)))))
(let ((a
;; 指定したアドレスのサーバにクロージャを送る。
;; 関数のロジックだけじゃなくて、nの持つ状態も一緒に。
(on "localhost:5000" (lambda () (f (+ n 1)))))
(b
(on "localhost:5000" (lambda () (f n)))))
;; 結果を使う。
(/ (thread-join! a) (thread-join! b))))(print (test 1000) "\n")
ubuntu(debian)インストールの問題
debianパッケージはschemeライブラリをここに入れるが/usr/lib/gambc4/ gambitは一つのインストレーションディレクトリ下にlib/があることを想定している。 tarballからインストールした方がよさそうだ。
これで全てが/usr/local/Gambit-C/に収まる。
$ wget http://www.iro.umontreal.ca/~gambit/download/gambit/v4.6/source/gambc-v4_6_0-devel.tgz
$ tar xzf gambc-v4_6_0-devel.tgz
$ cd gambc-v4_6_0-devel/
$ ./configure
# --enable-single-hostを使った方が速くメモリ効率の良いものができると言われた
$ ./configure --enable-single-host
$ make
$ sudo find /usr/ | sort > x.0
$ sudo make install
$ sudo find /usr/ | sort > x.1; comm -23 x.1 x.0 > PORTY