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

  • 例 (##namespace ("foo#"))とするとhogeがfoo#hogeになる
  • 例 (##namespace ("bar#" a b))とするとaとbだけにおいて有効になる?

モジュール機能はこのネームスペースを使って実装しているみたい。

スレッド

  • 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からインストールした方がよさそうだ。


$ 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
これで全てが/usr/local/Gambit-C/に収まる。

次回

Scheme Infix eXtension (SIX)
Foreign Function Interface (FFI)