maximaとか(3)

FreeBSDのVer.9がそろそろと噂だけど、予定通り遅れるそうです。これ、規定路線です。 そんなんでrubyのデフォは、今まで1.8系だったんだけど、新規一転1.9にしますかねと、 メンテナが立ち上がった。

ports/UPDATINGによれば

20110822:
  AFFECTS: users of lang/ruby
  AUTHOR: stas@FreeBSD.org

  The default ruby version has been updated to 1.9. Please rebuild all ports that
  depends on it.

  If you use portmaster:
  # portmaster -o lang/ruby19 lang/ruby18
  # portmaster -R -r ruby-1.9

  If you use portupgrade:
  # portupgrade -f lang/ruby18
  # portupgrade -f lang/ruby19 # if you have it installed
  # portupgrade -f ports-mgmt/portupgrade
  # portupgrade -x ruby-1.8.\* -fr lang/ruby18

  After these steps are complete, you can pkg_delete ruby 1.8 if you
  no longer need it.

  If you wish to keep the 1.8 version as default, add the following lines
  to your /etc/make.conf file, and rebuild lang/ruby18 after that.

  #
  # Keep ruby 1.8 as default version.
  #
  RUBY_DEFAULT_VER=1.8

と、高らかに宣言されました。これで、やっと1.8からおさらばか、長い間ご苦労さんと思って いたら、

20110823:
  AFFECTS: users of lang/ruby
  AUTHOR: swills@FreeBSD.org

  The default ruby version has been reverted from 1.9 to 1.8. If you followed
  the procedure in the 20110822 entry, you may need to follow these steps:

あろう事か、翌日にやっぱり元に戻すよと、屈辱?の宣言が行われました。原因は、1.9では 動かない膨大なrubyアプリが有り、方々から時期尚早だと非難が有ったからですかね。泣いてる 人を何人も見ましたから、確かな事でしょう。

おいらは、ruby依存体質にはなっていないんだけど(長い年月をかけてruby中毒から生還しました)、 新しいのにしましたよ。こういうトリガーが無ければ、やりませんからね。

[sakae@cdr ~]$ ruby -v
ruby 1.9.2p290 (2011-07-09 revision 32553) [i386-freebsd8]

Headを追いかける元気は有りませんです。

rubyに限らず、新しいものへの世代交代には時間がかかるものですから、気楽にやろーよと、 あの方も仰ってます。

Ryo  「ありがとうございます。えぇと、GuidoさんはPython3.0に世の中での
      メインストリームになるまで何年かかると考えていますか?」
Guido「5年だよ。Python2.xの時だって、マイナーバージョン+1の度におよそ1年で
      メインストリームが移り変わってきた。
      その傾向を見ると、今はPython3を使う人だって着実に増えているし、
      5年以内に3.0に移行するだろうと私は考えている。
      Googleでも3に移行するつもりの人が殆どの筈だよ。」
Ryo  「なるほど!つまりGoogleは5年以内にPython3xに移行するということですね!」
Guido「ハハハ,そう来たか!今のは私の完全な個人的な思想であって、Googleの構想や
      決定とは一切関係ない事を補足しておくよ。」

ちょいとschemeしてみる

前回、maxima内の階乗のソースを見た時、面白い事が書いてあった。再帰の説明に必ず出てくる やつは、遅いよと。へー、そうなんだ、これは試してみるしか。オリジナルは顧問リスプだけど、 ちょいとschemeに書き換えてやってみる。

Windowsには、gaucheも入っているけど、ここは、Diskの肥やしになりつつある、Petiteを 使う事にしました。

Petite Chez Scheme Version 8.0
Copyright (c) 1985-2010 Cadence Research Systems

Linked with Pthreads-Win32 under GNU LGPL
 http://sources.redhat.com/pthreads-win32/
 Copyright (c) 1998 John E. Bossom
 Copyright (c) 1999,2002 Pthreads-win32 contributors

> (define (k n m)
    (if (<= n m)
        n
        (* (k n (* 2 m))
            (k (- n m) (* 2 m)))))
> (define (fn n)
    (if (zero? n)
        1
        (k n 1)))
> (define (fo n)
    (if (zero? n)
        1
        (* n (fo (- n 1)))))

foは、普通の階乗で、fnの方は速いと評判?のやつだ。どんな動きをするかトレースしてみる。

> (trace k)
(k)
> (fn 5)
|(k 5 1)
| (k 4 2)
| |(k 2 4)
| |2
| |(k 4 4)
| |4
| 8
| (k 5 2)
| |(k 3 4)
| |3
| |(k 5 4)
| | (k 1 8)
| | 1
| | (k 5 8)
| | 5
| |5
| 15
|120
120

深い再帰はしないのかな? 普通の方はどうだろう?

> (trace fo)
(fo)
> (fo 5)
|(fo 5)
| (fo 4)
| |(fo 3)
| | (fo 2)
| | |(fo 1)
| | | (fo 0)
| | | 1
| | |1
| | 2
| |6
| 24
|120
120

そんじゃ、実行時間を計ってみる。

> (define (run f n)
    (time (f n))
    #t)

普通に時間を計る関数にS式を渡しちゃうと、S式の結果(今の場合は、階乗の結果)が表示 されちゃうので、ちょいと仕掛けをした。

> (run fo 30000)
(time (f n))
    73 collections
    1996 ms elapsed cpu time, including 0 ms collecting
    2020 ms elapsed real time, including 17 ms collecting
    716003648 bytes allocated, including 715217296 bytes reclaimed
#t
> (run fn 30000)
(time (f n))
    no collections
    328 ms elapsed cpu time
    336 ms elapsed real time
    843808 bytes allocated
#t

普通の方は、しっかりとごみ集めが発動されて、2秒もかかっている。ごみ撒き散らしバージョン だな。それに対して、新種の方は、5倍以上も速いよ。看板に偽り無しの結果になりました。

この際だからpetiteの新版が無いか確認しとくか。ああ、新しいのが出ていた。 ついでなので、WindowsのデフォルトSchemeに指定しておこう。(勿論、unix系はgaucheですが。) emacsから使うには、petite.batを書いて、.emacsをちょいと変更すればいいんだな。

@echo off
"C:\Program Files\Chez Scheme Version 8.3\bin\ti3nt\petite.exe"
(setq scheme-program-name "c:/Windows/petite.bat")
;; (setq scheme-program-name "gosh -i")

maximaの資料

ここらで、ちゃんとしたmaximaの資料を上げておこう。

まずは、本家本元のマニュアルを日本語化したもの。

次は、工学社から出ている『はじめてのMaxima』と 言う本。Lispの説明から始まって、数学、Maxima そして、周辺まで扱っている。買いたいなあと 思っていたら、PFDで改定版が公開されてた。

1000ページにもなる大著で、筆者のなみなみならぬ度量と熱意に感謝します。

後、こちらのリンクが充実してました。

SLIME入れて

上の大著を読み始めたんですけど、

最後のMS-Windows 派の方は, インストールも簡単なので何も特別に書かなくても
良い…としたいところですが, Maxima のインストールが簡単でも自分で工夫をしは
じめると一番苦労しなければならない実に不幸な環境です.
でも, 福音があります. 「VMware Player」や「VirtualBox」を使えばMS-Windows
環境上でもLinux を動かすことが出来ます.  そうすれば両方の楽しい所だけを堪能
できますし, 他のソフトの連動や動作の確認といったこともできるでしょう.  仮想計
算機が利用できる環境であれば, この際にVMware Player やVitrualBox を使って
KNOPPIX で遊ぶのはいかがでしょうか?

なんて、Windowsを使う人を哀れんでくれています。解決は著者も言っておられるように KNOPPIX/Mathを、仮想環境で動かしちゃう事 なんだけど、起動に時間がかかると言う難点があります。(贅沢ないいがかりだと言うのは 承知してます)仮想マシンをサスペンド/レジュームするって言う手もあるけど、ネットが 切れたり、時間が狂ったりで以前試してみたけど、やめちゃいました。(そんなのスクリプト 一発で解決じゃんと言うのは、Linux無知なおいらには難しい相談です。)

そんな訳で、不幸なOSに付き合います。土台のcclがemacs上から快適に使えれば、余は満足じゃ の 心境ですな。

Lispをemacsから使うなら、何は無くともSLIME一択でしょう。 SLIME: The Superior Lisp Interaction Mode for Emacs から取ってきて、それを展開。.emacs.dの中へ入れる。.emacsに

;;; ccl and slime ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(setq inferior-lisp-program "C:/app/ccl-1.7/wx86cl")
(add-to-list 'load-path (expand-file-name "~/.emacs.d/slime"))
(require 'slime)
(slime-setup '(slime-repl slime-fancy slime-banner))
(setq slime-net-coding-system 'utf-8-unix)

後は、M-x slimeで起動するだけ。

C-c C-c で、一つのS式をコンパイル。C-c C-kで、現在のファイルをコンパイル。C-c C-zで 現在のREPLを表示。困った時はリスターとですね。M-x slime-restart-inferior-lisp

どかこにチートシート (カンニングペーパー)が有ったな。 このあたりが良いかな。短縮URLは嫌いだけど。 そして、こちらではSLIMEが熱く語られています。

ついでに、こちらも見て設定を 強化しておいた方が良いかな。

cclと戯れる

折角、環境が出来たんで、cclでも階乗をチェックしてみます。schemeと同じように、runを 定義すると

(defun run (f n)
  (time (f n))
  t)
;Compiling "c:/homes/WORK/aaa.lisp"...
;Compiler warnings for "c:/homes/WORK/aaa.lisp" :
;   In RUN: Unused lexical variable F
;Compiler warnings for "c:/homes/WORK/aaa.lisp" :
;   In an anonymous lambda form inside RUN: Undefined function F

source側の、(f n) の所に、黄色いアンダーラインが引かれてしまい、コンパイルが失敗して しまいました。逃げの手を考えなくては。。

(defun run (f n)
  (time (apply f n))
  t)

こうやったら、20msでコンパイルが完了しました。さあ、走れコータロー。

CL-USER> (run fo '(5))

Unbound variable: FO
   [Condition of type UNBOUND-VARIABLE]

fo が、変数と看做されちゃってるよ。そうか、顧問リスプは、Lisp-2族なんで、変数空間と関数 空間が別々なってたんだ。で、変数空間を見て、そんなの無いじゃんと文句垂れてるんだな。

CL-USER> (run #'fn '(30000))
(APPLY F N) took 187 milliseconds (0.187 seconds) to run 
                    with 1 available CPU core.
During that period, 187 milliseconds (0.187 seconds) were spent in user mode
                    0 milliseconds (0.000 seconds) were spent in system mode
 876,064 bytes of memory allocated.
T
CL-USER> (run #'fo '(30000))
(APPLY F N) took 4,275 milliseconds (4.275 seconds) to run 
                    with 1 available CPU core.
During that period, 4,056 milliseconds (4.056 seconds) were spent in user mode
                    125 milliseconds (0.125 seconds) were spent in system mode
1,810 milliseconds (1.810 seconds) was spent in GC.
 715,228,968 bytes of memory allocated.
T

関数空間を見るように、#' のリーダーマクロを頭に付けてあげたら、やっと動いたよ。これだから Lisp-2は面倒臭いね。やっぱり、RubyやPythonみたいに、Lisp-1族が楽ね。

で、肝心の結果だけど、20倍も早くなってる。オリジナルの方は、しっかりガベコレが発動してるなあ。 で、プチ気になるのが、with 1 available CPU core. の下り。 京速コンピュータみたいに、石を いっぱい積めば、with 80000 available CPU core. なんて言う具合になるのかな?

でも、リーダーマクロ印を付けたり、applyの第二引数の決まり(リストで渡す)から、クォートを 付けたりしなきゃいけないのには、我慢がならんな。はて、どうすべ?

On Lisp とか LOLとか

そうだ、マクロだ。メタプログラミングだ。Rubyに負けないぞ。

(defmacro meas (f n)
  `(progn
     (time (,f ,n))
     t))

バッククォートで囲んで、実行したいS式を書く。そのS式の中で、展開したい引数の前にカンマを 書く。すると、そこだけが展開される。確認は、

CL-USER> (macroexpand-1 '(meas fo 5))
(PROGN (TIME (FO 5)) T)
T

どうやら、良さそうなので、本試験。

CL-USER> (meas fo 3000)
(FO 3000) took 31 milliseconds (0.031 seconds) to run 
                    with 1 available CPU core.
During that period, 31 milliseconds (0.031 seconds) were spent in user mode
                    0 milliseconds (0.000 seconds) were spent in system mode
 5,316,688 bytes of memory allocated.
T
CL-USER> (meas fn 3000)
(FN 3000) took 0 milliseconds (0.000 seconds) to run 
                    with 1 available CPU core.
During that period, 0 milliseconds (0.000 seconds) were spent in user mode
                    0 milliseconds (0.000 seconds) were spent in system mode
 56,912 bytes of memory allocated.
T

やっと、schemeと同等になったよ。疲れるなぁー。今度はガベコレが発生しないぐらいの仕事を 与えて、本来の効率を確認した。新方式は、タイマーのスレッショルド以下の時間で終了して ます。新方式は何度か実行すると、実行時間が16msなんてのが出てきた。T=16msの世界に おいらは住んでるのかな。旧と新の違いはメモリーの使いっぷりが10倍も違うなあ。

普通、時間を取るか空間効率を取るかの選択をすると、時間効率(実行スピード)を求めれば メモリーを大量に使うんだけど、今回はそれが否定されてる。こういう事も有るのね。この秘密は 何処に?

CL-USER> (disassemble #'fo)
;; "c:/homes/WORK/aaa.lisp":154-220
     [0] (recover-fn)
L5
     [5] (cmpl ($ 4) (% temp1))
     [8] (jne L162)
    [14] (pushl (% ebp))
    [15] (movl (% esp) (% ebp))
    [17] (pushl (% arg_z))

;;; (zerop n)
    [18] (movl (% arg_z) (% arg_y))
    [20] (testl ($ 3) (% arg_y))
    [26] (jne L43)
    [28] (testl (% arg_y) (% arg_y))
    [30] (movl ($ #x1300E) (% imm0))
    [35] (leal (@ -13 (% imm0)) (% arg_z))
    [38] (cmovel (% imm0) (% arg_z))
    [41] (jmp L60)
L43
    [43] (xorl (% arg_z) (% arg_z))
    [45] (leal (@ 0 (% arg_y)) (% arg_y))
    [49] (calll (@ .SPBUILTIN-EQ))
    [55] (recover-fn)
L60
    [60] (cmpl ($ #x13001) (% arg_z))
    [66] (je L75)

;;; (if (zerop n) 1 (* n (fo (1- n))))
    [68] (movl ($ 4) (% arg_z))
    [73] (jmp L156)

;;; (1- n)
L75
    [75] (movl (@ -4 (% ebp)) (% arg_z))
    [78] (testb ($ 3) (% arg_z.b))
    [81] (jne L102)
    [83] (addl ($ -4) (% arg_z))
    [86] (jno L124)
    [88] (nop)
    [89] (calll (@ .SPFIX-OVERFLOW))
    [95] (recover-fn)
   [100] (jmp L124)
L102
   [102] (movl ($ #xFFFFFFFC) (% arg_y))
   [107] (leal (@ 0 (% arg_y)) (% arg_y))
   [113] (calll (@ .SPBUILTIN-PLUS))
   [119] (recover-fn)

;;; (fo (1- n))
L124
   [124] (movl ($ 4) (% temp1))
   [129] (nop)
   [130] (calll L5)
   [135] (recover-fn)

;;; (* n (fo (1- n)))
   [140] (movl (@ -4 (% ebp)) (% arg_y))
   [143] (movl (% arg_y) (% arg_y))
   [145] (calll (@ .SPBUILTIN-TIMES))
   [151] (recover-fn)

;;; (if (zerop n) 1 (* n (fo (1- n))))
L156
   [156] (leavel)
   [157] (retl)

;;; #<no source text>
L162
   [162] (uuo-error-wrong-number-of-args)

ははは、悪い物見ちゃったな。オペランドに出てくる一文字は何だろう? 探してみたら こんな所に出ていた。気にする人なんて、そんなに居ないからいいけど。

気を取り直して、ついでにマクロで使われる、カンマ・アットも見ておく。

CL-USER> (setq b '(1 2 3))
(1 2 3)
CL-USER> `(a ,b c)
(A (1 2 3) C)
CL-USER> `(a ,@b c)
(A 1 2 3 C)

カンマ・アットは、一番外側のカッコ・コッカを取り除いてから、置き換えてくれる。

と、まあ、マクロは素晴らしいんだけど、健康の為に使いすぎに注意しましょう。普通の人に 取っては使わないで済めば使わないに越した事です。普通の人意外になりたかったら、ご自由に。

SLIMEのおまけ

顧問Lispの本見てたら、SLIMEを使うと簡単にマクロを展開確認出来るよなんて事が書いて あった。折角なので、やってみる。題材は、上で出てきたmeasのマクロ。大胆に、(defmacro meas の、カッコの所ににカーソルを置き、C-c RET するだけ。

(EVAL-WHEN (:COMPILE-TOPLEVEL :LOAD-TOPLEVEL :EXECUTE)
  (SB-C::%DEFMACRO 'MEAS
                   #'(SB-INT:NAMED-LAMBDA (DEFMACRO MEAS)
                         (#:WHOLE897 #:ENVIRONMENT898)
                       (DECLARE (IGNORE #:ENVIRONMENT898))
                       (LET* ()
                         (DECLARE (MUFFLE-CONDITIONS CODE-DELETION-NOTE))
                         (LET ((#:ARGS900 (CDR #:WHOLE897)))
                           (UNLESS
                               (SB-INT:PROPER-LIST-OF-LENGTH-P #:ARGS900 2 2)
                             (SB-KERNEL::ARG-COUNT-ERROR 'DEFMACRO 'MEAS
                                                         #:ARGS900 '(F N) 2
                                                         2)))
                         (LET* ((F (CAR (CDR #:WHOLE897)))
                                (N (CAR (CDR (CDR #:WHOLE897)))))
                           (BLOCK MEAS `(PROGN (TIME (,F ,N)) T)))))
                   '(F N) NIL '(MACRO-FUNCTION MEAS)))

defmacro手続き自身もマクロで書いてあるんで、この場合は、defmacroを展開した 結果が見えています。頭に#: が付いているのがgensymからの出力ね。

で、まだ完全に展開されてないな。SB-Cのパッケージって何が 詰めあわされているのだろう。そのうちに、徹底的に中身を曝け出してみるかな。