CMUCL - CMU Common Lisp
CMU Common Lisp 紹介
Allegro の体験版はライセンス更新がめんどいな〜っと思ってたところに cmucl なんて Common Lisp のコンパイラを発見.すげー.使い始めたばっかなので基本的にマニュアルで調べた事をメモっときます.
個人的に嬉しい順 18d -> 18e での変更点 (release-18e.txt より)
- TIME マクロで Pentium/UltraSPARC の カウンタ情報が見えるようになった (TIME (expt 2 100)) とか
- 巨大な多倍長整数に対する乗算が速くなった
- 外部ライブラリとのリンクがまともになった(ダンプしても OK).…らしいが,うまくいかない.なんだろう?
- 今迄決め打ちされてたイメージファイルのパスが相対パスになったため,標準配布物が relocatable になった.これで管理者権限無い環境でもインストールが楽.
- PROFILE:REPORT-TIME の表示がすっきりした
- コンパイラの出力が ; から始まるようになったため,Emacs + ilisp 等での見やすさが向上
- EXT:*BYTES-CONSED-BETWEEN-GCS のデフォルト値が 2000000 から 12000000 に.少なすぎると思ってたよ.
- その他,Bugfix,ANSI Common Lisp との整合性が改善とか.
CMU Common Lisp の未来(from CVS)
19a で取りこまれる予定の機能
- Package のロック機構
- Simple Stream の実装
- CLOS のより高速な実装(Gred's PCL)
- スタンドアロン実行イメージの作成機能(lisp-executable ブランチで作業中間に合わないでしょう
どうやら 19a のリリースは 12 月半ばあたりになる予定.lisp-executable ブランチはマージされるのだろうか…. とか書いてたら CVS サーバーマシンが落ちて順調に遅れてます。
CMU Common Lisp CVS 版のビルド
2004-03-22 版 on FreeBSD 4.9
common-lisp.net から 入手できるようになったばかりの CVS 版。ビルドには 2003 年 12 月の スナップショットのバイナリを使用。
- 2003/12/03 日のスナップショットからの変更点 (cvslog.lisp を使って生成)
;; from CMUCL CVS 2003-12-03 (bootfiles 使わないでチャレンジ) % cvs -d :pserver:anonymous@common-lisp.net:/project/cmucl/cvsroot login ;; password: anonymous % cvs -d :pserver:anonymous@common-lisp.net:/project/cmucl/cvsroot co src % ./src/tools/create-target.sh freebsd FreeBSD_gencgc FreeBSD % vi freebsd/setenv.lisp % ./src/tools/build-world.sh freebsd % ./src/tools/rebuild-lisp.sh freebsd % ./src/tools/build-world.sh freebsd % ./src/tools/load-world.sh freebsd "pre19a `date \"+ %Y%m%d\"`" % ./src/tools/build-util.sh freebsd % ./src/tools/make-dist.sh -g freebsd pre19a x86 freebsd ;; これで ;; cmucl-pre19a-x86-freebsd.extra.tar.gz ;; cmucl-pre19a-x86-freebsd.tar.gz ;; ができる。-b オプションを使うと bzip を使ってアーカイブしてくれる。
MULTIPROCESSING パッケージ
パッケージ概要
Lisp 上でマルチスレッドを実現するためのパッケージ. この Multiprocessing (MP) パッケージは現在 x86 専用です. これは Lisp で実現されているスレッドであるため,x86 上の CMUCL なら環境を問 わず使える…ような気がしますが,念のため *features* を確認してください.
で,注意すべきなのは mp::idle-process-loop のドキュメント部分にあるコレです.
"idle loop to be run by the initial process. The select based event server is called with a timeout calculated from the minimum of the idle-loop-timeout and the time to the next process wait timeout. To avoid this delay when there are runnable processes the idle-process should be setup to the initial-process. If one of the processes quits by throwing to %end-of-the-world then quitting-lisp will have been set to the exit value which is noted by the idle loop which tries to exit gracefully destroying all the processes and giving them a chance to unwind."
よーするに idle-process-loop は initial process によって実行されなきゃならん わけです.initial process を idle-process-loop で使っちゃったら操作できない じゃん!! というわけで,対話的に使いたい場合は top-level-loop を make-process して動かしておくわけですが,mp パッケージに入っている startup-idle-and-top-level-loops という関数を使えばこの辺が一発でできて便利 です.
使用例
* (mp::startup-idle-and-top-level-loops)
* (defun foo () (sleep 10) (print 'foo) (foo))
foo
* (defun bar () (sleep 20) (print 'bar) (foo))
bar
* (mp:make-process #'foo :name "foo-1")
#<Process foo-1 {48908825}>
* (mp:make-process #'bar :name "bar-1")
#<Process bar-1 {48912FED}>
*
foo
foo
bar
foo
foo
MP パッケージの(公開されてる)関数
で,cmucl-user マニュアルを見たら…あら?記述がない?というわけで,どうやら ドキュメントは documentation で読める分とソースコードしかない模様. src/multi-proc.lisp から public となってるやるを↓にまとめてみました.
(* 関数 *)
process-whostate (process)           - process の状態を返す
process-active-p (process)           - process が Active かどうかを調べる
process-alive-p (process)            - process が生きている(:active 又は :inactive)
                                       かどうかを調べる
current-process ()                   - カレントプロセスを返す
all-processes ()                     - 全てのプロセスのリストを返す
make-process (function &key (name "Anonymous")
                            (run-reasons (list :enable))
                            (arrest-reasons nil)
                            (initial-bindings nil))
                                     - function を実行する process を作成する
process-interrupt (process function) - process に割り込んで function を評価する
destroy-process (process)            - process を削除する
disable-process (process)            - process を :inactive にする
enable-process (process)             - process を :active にする
(process-wait (whostate predicate &rest args)
                                     - process は predicate が真を返すまで wait する.
process-wait-with-timeout (whostate timeout predicate &rest args)
                                     - process は predicate が真を返すか,timeout で
                                       指定した秒数が過ぎるまで wait する.
process-yield ()                     - 他のプロセスに実行を譲る
process-run-time (process)           - プロセスの実行時間を返す
process-idle-time (process)          - idle 時間を返す
process-wait-until-fd-usable (fd direction &optional timeout)
                                     - FD が direction に対して利用可能になるまで wait
                                       して真を返す.timeout 秒過ぎた場合は nil を返す.
sleep (n)                            - N 秒間サスペンドする.N は非負,非複素数の数.
                                      (つまり小数でも OK)
show-processes (&optional verbose)   - 全てのプロセスとその状態を表示する.
                                       verbose が t ならさらに run, real, idle 時間を表示する.
(* マクロ *)
without-scheduling (&body body)      - body を実行中はスケジューリングをしない
with-timeout ((timeout &body timeout-forms) &body body)
                                     - body を実行し,body の最後のフォームの値を返す.
                                       しかし,もし実行に timeout よりも長い時間がかかったら,
                                       実行を中断して timeout-forms を評価し,その値を返す.
with-lock-held ((lock &optional (whostate "Lock Wait") &key (wait t) timeout) &body body)
                                     - lock を保持したまま body を実行.もし,lock が他のプロセス
                                       によって保持されていたなら lock が解放されるまで wait する.
                                       timeout は process-wait-with-timeout と同じ秒を受けとり,
                                       timeout に達っしたら nil が返る.
                                       wait が nil ならば,ロックが他のプロセスに保持されていた
                                       場合は body を処理せずに,すぐに nil が返る.
さらに,CMUCL のランタイムがインタラプトセーフじゃないので危険ですが,
start-sigalrm-yield (&optional (sec 0) (usec 500000))
                                    - process-yield をコールする SIGALRM 割り込みを開始する
なんてのもあるみたいです.ただし,MP パッケージからエクスポートされていない事からもわかるように,お勧めはできません.
REMOTE と WIRE パッケージ
パッケージ概要
REMOTE パッケージと WIRE パッケージは TCP/IP を使って実装されたプロセス間通信(IPC)を提供します.
使用例
;; A.lisp
(defconstant +A+ 5000)
(defconstant +B+ 5001)
(defparameter s (wire:create-request-server +A+))
(defun function-A (str)
  (with-output-to-string (s)
    (write-line "<function-A>" s)
    (write-line str s)    
    (write-line "</function-A>" s)))
;; B.lisp
(defconstant +B+ 5001)
(defparameter s (wire:create-request-server +B+))
(defun function-B1 (str)
  (with-output-to-string (s)
    (write-line "<function-B1>" s)
    (write-line str s)
    (write-line "</function-B1>" s)))
(defun function-B2 (str)
  (with-output-to-string (s)
    (write-line "<function-B2>" s)
    (write-line str s)
    (write-line "</function-B2>" s)))
;;; main.lisp
(defconstant +A+ 5000)
(defconstant +B+ 5001)
(defun process-A (str)
  (let ((s (wire:connect-to-remote-server "localhost" +A+)))
    (wire:remote-value s (function-A str))))
(defun process-B (str)
  (let* ((s (wire:connect-to-remote-server "localhost" +B+))
     (str (wire:remote-value s (function-B1 str))))
    (wire:remote-value s (function-B2 str))))
(defun main ()
  (let* ((str "original")
     (A (process-A str))
     (B (process-B A)))
    (print B)))
というファイルを準備し,CMUCL プロセスを別々に起動してそれぞれ A.lisp,B.lisp, main.lisp をロードします. そして main.lisp をロードしたプロセスで main 関数を呼べば
lambda% lisp * (load "main.lisp") t * (main) "<function-B2> <function-B1> <function-A> original </function-A> </function-B1> </function-B2> "
という結果が得られるハズです.
CLOCC のインストール
CLOCC 2004/03/20 日の CVS 版のインストールメモ。http://clocc.sourceforge.net/
clocc の入手
$ cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/clocc login $ cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/clocc checkout clocc
clocc-top と defsystem コンパイル
ホームディレクトリ以下の ~/commonlisp ディレクトリに clocc を展開 した場合の設定。ここは環境に応じて設定する。
$ cd clocc $ export LISPTYPE=cmucl $ vi clocc.lisp $ cvs diff Index: clocc.lisp =================================================================== RCS file: /cvsroot/clocc/clocc/clocc.lisp,v retrieving revision 1.22 diff -r1.22 clocc.lisp 112c112,113 < #-(or win32 winnt mswindows cygwin) "/usr/local/src/clocc/" --- > ;;#-(or win32 winnt mswindows cygwin) "/usr/local/src/clocc/" > #-(or win32 winnt mswindows cygwin) "home:/commonlisp/clocc/" $ gmake clocc-top
PORT
互換レイヤの PORT パッケージ。CLLIB 等のビルドに必要。
$ cd ../port $ gmake system
CLLIB
様々なライブラリ。一部 CMUCL で動作しない物があるので cllib.system を編集している。
$ cd ../cllib
$ vi cllib.system
$ cvs diff
cvs server: Diffing .
Index: cllib.system
===================================================================
RCS file: /cvsroot/clocc/clocc/src/cllib/cllib.system,v
retrieving revision 1.30
diff -r1.30 cllib.system
64c64
<      (:file "prompt" :depends-on ("base"))
---
>      #+clisp (:file "prompt" :depends-on ("base"))
67c67
<      (:file "server" :depends-on ("base" "log" "prompt"))
---
>      #+clisp (:file "server" :depends-on ("base" "log" "prompt"))
$ gmake system
SCREAMER
非決定的プログラミングをサポートする SCREAMER パッケージ。
$ cd ../screamer $ gmake system
ONLISP
Paul Graham 氏 の著作 OnLisp に掲載されていたコード。
$ vi package.lisp $ cvs diff cvs server: Diffing . Index: package.lisp =================================================================== RCS file: /cvsroot/clocc/clocc/src/onlisp/package.lisp,v retrieving revision 1.1.1.1 diff -r1.1.1.1 package.lisp 3c3 < (in-package :user) --- > (in-package :cl-user) $ lisp * (compile-file "package") * (load "package") * (compile-file "onlisp-util") * (compile-file "onlisp-app")