SICP 読み (353) 5.5 翻訳系

問題 5.46

とりあえず fib を評価させてみた。

gosh> (add-load-path ".")
("." "/usr/share/gauche/site/lib" "/usr/share/gauche/0.8.7/lib")
gosh> (load "load-eceval-compiler")
#t
gosh> (load "ch5-compiler")
#t
gosh> (define true #t)
true
gosh> (define false #f)
false
gosh> (compile-and-go '(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))

(total-pushes = 0 maximum-depth = 0)
;;; EC-Eval value:
ok

;;; EC-Eval input:
(fib 0)

(total-pushes = 7 maximum-depth = 3)
;;; EC-Eval value:
0

;;; EC-Eval input:
(fib 1)

(total-pushes = 7 maximum-depth = 3)
;;; EC-Eval value:
1

;;; EC-Eval input:
(fib 2)

(total-pushes = 17 maximum-depth = 5)
;;; EC-Eval value:
1

;;; EC-Eval input:
(fib 3)

(total-pushes = 27 maximum-depth = 8)
;;; EC-Eval value:
2

;;; EC-Eval input:
(fib 4)

(total-pushes = 47 maximum-depth = 11)
;;; EC-Eval value:
3

;;; EC-Eval input:
(fib 5)

(total-pushes = 77 maximum-depth = 14)
;;; EC-Eval value:
5

;;; EC-Eval input:
(fib 6)

(total-pushes = 127 maximum-depth = 17)
;;; EC-Eval value:
8

;;; EC-Eval input:
(fib 7)

(total-pushes = 207 maximum-depth = 20)
;;; EC-Eval value:
13

;;; EC-Eval input:
(fib 8)

(total-pushes = 337 maximum-depth = 23)
;;; EC-Eval value:
21

;;; EC-Eval input:

ええと、とりあえず問題 5.28 の出力を(297)からコピーしておいて比べてみます。

;;; EC-Eval input:
(define (fib n)
  (if (< n 2)
      n
      (+ (fib (- n 1)) (fib (- n 2)))))

(total-pushes = 3 maximum-depth = 3)
;;; EC-Eval value:
ok

;;; EC-Eval input:
(fib 2)

(total-pushes = 72 maximum-depth = 13)
;;; EC-Eval value:
1

;;; EC-Eval input:
(fib 3)

(total-pushes = 128 maximum-depth = 18)
;;; EC-Eval value:
2

;;; EC-Eval input:
(fib 4)

(total-pushes = 240 maximum-depth = 23)
;;; EC-Eval value:
3

;;; EC-Eval input:
(fib 5)

(total-pushes = 408 maximum-depth = 28)
;;; EC-Eval value:
5

;;; EC-Eval input:
(fib 6)

(total-pushes = 688 maximum-depth = 33)
;;; EC-Eval value:
8

;;; EC-Eval input:
(fib 7)

(total-pushes = 1136 maximum-depth = 38)
;;; EC-Eval value:
13

;;; EC-Eval input:
(fib 8)

(total-pushes = 1864 maximum-depth = 43)
;;; EC-Eval value:
21

;;; EC-Eval input:

ええとこっちの fib の場合は

  • fib(2) なら fib(0) の 16 と fib(1) の 16 に 40 を加えて 72 回
  • fib(3) なら fib(1) の 16 と fib(2) の 72 に 40 を加えて 128 回
  • fib(4) なら fib(2) の 72 と fib(3) の 128 に 40 を加えて 240 回
  • fib(5) なら fib(3) の 128 と fib(4) の 240 に 40 を加えて 408 回
  • fib(6) なら fib(4) の 240 と fib(5) の 408 に 40 を加えて 688 回
  • fib(7) なら fib(5) の 408 と fib(6) の 688 に 40 を加えて 1136 回
  • fib(8) なら fib(6) の 688 と fib(7) の 1136 に 40 を加えて 1864 回

で、これ式で翻訳したソレの数を見てみますと以下。

  • (fib 0) が (total-pushes = 7 maximum-depth = 3)
  • (fib 1) が (total-pushes = 7 maximum-depth = 3)
  • (fib 2) が (total-pushes = 17 maximum-depth = 5)
  • (fib 3) が (total-pushes = 27 maximum-depth = 8)
  • (fib 4) が (total-pushes = 47 maximum-depth = 11)
  • (fib 5) が (total-pushes = 77 maximum-depth = 14)
  • (fib 6) が (total-pushes = 127 maximum-depth = 17)
  • (fib 7) が (total-pushes = 207 maximum-depth = 20)
  • (fib 8) が (total-pushes = 337 maximum-depth = 23)

fib(n) の push 回数は

(+ (fib(- n 1) の push 回数) (fib(- n 2) の push 回数) 3) 

というカンジでしょうか。意味はありませんが arc で検算。

$ mzscheme -m -f as.scm
Use (quit) to quit, (tl) to return here after an interrupt.
arc> (+ 7 7 3)
17
arc> (+ 7 17 3)
27
arc> (+ 17 27 3)
47
arc> (+ 27 47 3)
77
arc> (+ 47 77 3)
127
arc> (+ 77 127 3)
207
arc> (+ 127 207 3)
337
arc> 

合ってる模様。これはしかしアレですな違いを列挙してみると

  • fib(0) とか fib(1) の push 回数が解釈系だと 16 で翻訳系だと 7
  • (+ (fib (- n 1)) (fib (- n 2))) の演算で必要な push の回数が解釈系だと 40 で翻訳系だと 3

むむ、えらい効率良いな、と言いつつひさびさの散髪に行ってまいります。年末に散髪に行きそびれたんですがいつから行ってないんだろ。

帰宅後

なんでこんなに違うのか、と言いつつ fib なソレを compile してみた。何度も同じコトしてて微妙ですが整形したナニを以下に。

gosh> (compile '(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) 'val 'return)
((env continue) (val) 
 ((assign val (op make-compiled-procedure) (label entry1) (reg env)) 
  (goto (label after-lambda2)) 

  entry1 
  (assign env (op compiled-procedure-env) (reg proc)) 
  (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) 
  (save continue) 
  (save env) 
  (assign proc (op lookup-variable-value) (const <) (reg env)) 
  (assign val (const 2)) 
  (assign argl (op list) (reg val)) 
  (assign val (op lookup-variable-value) (const n) (reg env)) 
  (assign argl (op cons) (reg val) (reg argl)) 
  (test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch6)) 
  compiled-branch7 
  (assign continue (label after-call8)) 
  (assign val (op compiled-procedure-entry) (reg proc)) 
  (goto (reg val)) 
  primitive-branch6 
  (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) 
  after-call8 
  (restore env) 
  (restore continue) 

  (test (op false?) (reg val)) 
  (branch (label false-branch4)) 
  true-branch3 
  (assign val (op lookup-variable-value) (const n) (reg env)) 
  (goto (reg continue)) 
  false-branch4 
  (assign proc (op lookup-variable-value) (const +) (reg env)) 
  (save continue) 
  (save proc) 
  (save env) 
  (assign proc (op lookup-variable-value) (const fib) (reg env)) 
  (save proc) 
  (assign proc (op lookup-variable-value) (const -) (reg env)) 
  (assign val (const 2)) 
  (assign argl (op list) (reg val)) 
  (assign val (op lookup-variable-value) (const n) (reg env)) 
  (assign argl (op cons) (reg val) (reg argl)) 
  (test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch15)) 
  compiled-branch16 
  (assign continue (label after-call17)) 
  (assign val (op compiled-procedure-entry) (reg proc)) 
  (goto (reg val)) 
  primitive-branch15 
  (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) 
  after-call17 
  (assign argl (op list) (reg val)) 
  (restore proc) 
  (test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch18)) 
  compiled-branch19 
  (assign continue (label after-call20)) 
  (assign val (op compiled-procedure-entry) (reg proc)) 
  (goto (reg val)) 
  primitive-branch18 
  (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) 
  after-call20 
  (assign argl (op list) (reg val)) 
  (restore env) 
  (save argl) 
  (assign proc (op lookup-variable-value) (const fib) (reg env)) 
  (save proc) 
  (assign proc (op lookup-variable-value) (const -) (reg env)) 
  (assign val (const 1)) 
  (assign argl (op list) (reg val)) 
  (assign val (op lookup-variable-value) (const n) (reg env)) 
  (assign argl (op cons) (reg val) (reg argl)) 
  (test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch9)) 
  compiled-branch10 
  (assign continue (label after-call11)) 
  (assign val (op compiled-procedure-entry) (reg proc)) 
  (goto (reg val)) 
  primitive-branch9 
  (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) 
  after-call11 
  (assign argl (op list) (reg val)) 
  (restore proc) 
  (test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch12)) 
  compiled-branch13 
  (assign continue (label after-call14)) 
  (assign val (op compiled-procedure-entry) (reg proc)) 
  (goto (reg val)) 
  primitive-branch12 
  (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) 
  after-call14 
  (restore argl) 
  (assign argl (op cons) (reg val) (reg argl)) 
  (restore proc) 
  (restore continue) 
  (test (op primitive-procedure?) (reg proc)) 
  (branch (label primitive-branch21)) 
  compiled-branch22 
  (assign val (op compiled-procedure-entry) (reg proc))
  (goto (reg val)) 
  primitive-branch21 
  (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) 
  (goto (reg continue)) 
  after-call23 
  after-if5 
  after-lambda2 

  (perform (op define-variable!) (const fib) (reg val) (reg env)) 
  (assign val (const ok)) 
  (goto (reg continue)))

長いなぁ。ってなんで fib(0) とか fib(1) とかの total-pushes が 7 なの??
で、total-pushes って何だったかな、と言いつつ ch5-regsim.scm の make-stack 手続きに至る。単純に push された回数だなぁ。これって eceval の中での push の回数も入ってると見た。違うかなぁ。
という事で検証してみる。どうやるかなあ。とりあえず最初は compile-and-go で val に compile->assemble された命令列をセットして flag に true をセットして eceval が start して以下の命令を辿る

  (assign compapp (label compound-apply))
;; flag が true なので jmp する
  (branch (label external-entry))

external-entry
  (perform (op initialize-stack))
  (assign env (op get-global-environment))
  (assign continue (label print-result))
  (goto (reg val))

ここで compile->assemble された手続きに制御が移りますが push はナシ。その後、print-result に戻って以下。

print-result
  (perform (op print-stack-statistics))
  (perform
   (op announce-output) (const ";;; EC-Eval value:"))
  (perform (op user-print) (reg val))
  (goto (label read-eval-print-loop))
read-eval-print-loop
  (perform (op initialize-stack))
  (perform
   (op prompt-for-input) (const ";;; EC-Eval input:"))
  (assign exp (op read))

で入力待ちになる、と。この次に例えば (fib 0) とか入力された場合どうなるか、というと

;; exp には (fib 0) がセット

  (assign env (op get-global-environment))
  (assign continue (label print-result))
  (goto (label eval-dispatch))

;; eval-dispatch では application 認定

ev-application
  (save continue)
  (save env)
  (assign unev (op operands) (reg exp))
  (save unev)
  (assign exp (op operator) (reg exp))
  (assign continue (label ev-appl-did-operator))
  (goto (label eval-dispatch))

;; push 3 回
;; eval-dispatch では variable 認定 (fib)

ev-variable
  (assign val (op lookup-variable-value) (reg exp) (reg env))
  (goto (reg continue))

;; ev-appl-did-operator に戻る

ev-appl-did-operator
  (restore unev)
  (restore env)
  (assign argl (op empty-arglist))
  (assign proc (reg val))
  (test (op no-operands?) (reg unev))
  (branch (label apply-dispatch))
  (save proc)

;; push 1 回

ev-appl-operand-loop
  (save argl)
  (assign exp (op first-operand) (reg unev))
  (test (op last-operand?) (reg unev))
  (branch (label ev-appl-last-arg))

;; push 1 回
;; llast-operand なので ev-appl-last-arg に jmp

ev-appl-last-arg
  (assign continue (label ev-appl-accum-last-arg))
  (goto (label eval-dispatch))

;; eval-dispatch では self-eval 認定

ev-self-eval
  (assign val (reg exp))
  (goto (reg continue))

;; ev-appl-accum-last-arg に jmp

ev-appl-accum-last-arg
  (restore argl)
  (assign argl (op adjoin-arg) (reg val) (reg argl))
  (restore proc)
  (goto (label apply-dispatch))

以降は略。確かにこれで 7 回になるんだけど次の謎が ...
とりあえず買物に行くらしいのでひとまず中断。次の謎は何故に fib(2) の push 回数が 7 + 7 + 3 になるか、とゆー事ッス。

つづき

えーと、n が 0 とか 1 の (< n 2) が真なケイスでは fib の呼び出しまでに 5 回 push されて < な演算で 2 回 push で手続きが終わるので計 7 回。でも n が 2 以上のケイスで 7 + 7 + 3 ってのはタマタマ数値がそうなってるだけで実は違うカンジ。
例えば n が 2 だったらどうなるんだろうか。基本的には

  • eceval で fib を apply するまでに 5 回
  • fib(0) で 2 回
  • fib(1) で 2 回

の 9 回の push が発生する事が予測できる。n が 2 の時の total-pushes が 17 なので残りは 8 ですか。カウントしてみると

  • (< n 2) なソレで 2 回 (これは n が 0 だの 1 だのと同様)
  • + を lookup した後に continue と proc と env を push で 3 回
  • 最初の fib の引数の評価 (- n 1) の前に proc を push で 1 回
  • 次の fib を lookup する前に argl を push で 1 回
  • 次の fib の引数の評価 (- n 2) の前に proc を push で 1 回

という事ですか。てーコトは

  • eceval で fib を apply するまでの 5 回
  • n が 2 以上の場合は (+ (fib (- n 1)) (fib (- n 2))) なナニで 8 回

他はそれぞれの fib のソレに必要な push の回数になる (ただし、eceval な処理に必要な push 階数は引く必要あり)、とゆー事は

  • (fib 3) なら (+ 5 8 2 (- 17 5)) で 27
  • (fib 4) なら (+ 5 8 (- 17 5) (- 27 5)) で 47
  • (fib 5) なら (+ 5 8 (- 27 5) (- 47 5)) で 77
  • (fib 6) なら (+ 5 8 (- 47 5) (- 77 5)) で 127
  • (fib 7) なら (+ 5 8 (- 77 5) (- 127 5)) で 207
  • (fib 8) なら (+ 5 8 (- 127 5) (- 207 5)) で 337

という事ですかそうですか。しかし 5 引くのは微妙だな。どっちかとゆーと

  • (fib 2) は (+ 5 (+ 2 2 8))
  • (fib 3) は (+ 5 (+ 2 (+ 2 2 8) 8))
  • (fib 4) は (+ 5 (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8)
  • (fib 5) は (+ 5 (+ 2 (+ 2 2 8) 8) (+ (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8) 8)
  • (fib 6) は (+ 5 (+ (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8) (+ (+ 2 (+ 2 2 8) 8) (+ (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8) 8) 8)
  • (fib 7) は (+ 5 (+ (+ 2 (+ 2 2 8) 8) (+ (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8) 8) (+ (+ (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8) (+ (+ 2 (+ 2 2 8) 8) (+ (+ 2 2 8) (+ 2 (+ 2 2 8) 8) 8) 8) 8) 8)

こうすれば、ではありますが微妙。もう少しカシコい書き方がありそげ。しまった書き方じゃなくてアレだな。冗長ですがもう一回書きます。

  • (fib 2) は (+ 5 (+ 2 2 8))
  • (fib 3) は (+ 5 (+ 2 12 8))
  • (fib 4) は (+ 5 (+ 12 22 8))
  • (fib 5) は (+ 5 (+ 22 42 8))
  • (fib 6) は (+ 5 (+ 42 72 8))

という書き方であれば分かりやすいのかなぁ。(誰

書き方云々じゃなくって有効性を決めよ、とあるんですがどうすりゃええのか分からんぞ。とりあえず問題 5.29 な S(n) = aFib(n+1) + b なソレは

  • S(2) = 2 * x + y = 17
  • S(3) = 3 * x + y = 27

になりますな。上記の連立方程式の解は x が 10 で y が -3 になる。ちなみに問題 5.29 な x は 56 で y が -40 だそうな。push 回数のトータルが

  • 翻訳系だと (+ (* 10 (fib (+ n 1))) -3)
  • 解釈系だと (+ (* 56 (fib (+ n 1))) -40)

で、単純に 5 倍ですかそうですか、と思ってたら fib な数値 (しかも次の要素) にかけ算されてるのでもっと、なんだな。
ちょっとこれ以上は教養な部分で限界なんで勘弁して下さひ。