CS 22
Clab 37: Efficient evaluation
Well, we have been lazy. Now let's try to be efficient.
Copy /home/cfk/pub/cs22/week12/aneval to your week13 directory.
This is the analyzing evaluator discussed in section 4.1.7. We'll
load it and
do a few evaluations and then look at the-global-environment.
metacircular-evaluator-loaded
analyzing-metacircular-evaluator-loaded
> (driver-loop)
;;; M-Eval input:
(define x 40)
;;; M-Eval value:
ok
;;; M-Eval input:
(define (square x) (* x x))
;;; M-Eval value:
ok
;;; M-Eval input:
(square x)
;;; M-Eval value:
1600
;;; M-Eval input:
(define (factorial n)
(cond
((= n 1) 1)
(else (* n (factorial (- n 1))))
)
)
;;; M-Eval value:
ok
;;; M-Eval input:
(factorial 4)
;;; M-Eval value:
24
;;; M-Eval input:
q
/home/cfk/cs22/week12/mceval.scm: 257.9-257.39: Unbound variable q
> the-global-environment
#1=(((factorial
square
x
false
true
car
cdr
cons
null?
list
memq
member
not
+
-
*
/
=
>
>=
<=
<
abs
remainder
integer?
sqrt
eq?)
(procedure (n) # #1#)
(procedure (x) # #1#)
40
#f
#t
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #>)
(primitive #=>)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)))
Note that fact and square are bound to the lists:
(procedure (n) # #1#)
(procedure (x) # #1#)
, respectively.
The procedures embedded in these lists are drscheme procedures,
that is procedures in our underlying machine language if you will.
They are not mceval procedures. We can test this by using drschemes
apply which mceval saved as apply-in-underlying-scheme. Recall that
apply-in-underlying-scheme takes two parameters, a function and a
list of arguments. It applies the function to the arguments.
> (apply-in-underlying-scheme + '(2 3))
5
> (lookup-variable-value 'factorial the-global-environment)
#2=(procedure
(n)
#
#4=(((factorial
square
x
false
true
car
cdr
cons
null?
list
memq
member
not
+
-
*
/
=
>
>=
<=
<
abs
remainder
integer?
sqrt
eq?)
#2#
(procedure (x) # #4#)
40
#f
#t
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #>)
(primitive #=>)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #))))
>
> (caddr (lookup-variable-value 'factorial the-global-environment))
#
> (apply-in-underlying-scheme
(caddr (lookup-variable-value 'factorial the-global-environment))
(list
(extend-environment
'(n)
'(5)
the-global-environment)))
120
Now let's compare this to what mceval would have given us at the same point.
> the-global-environment
#1=(((factorial
square
x
false
true
car
cdr
cons
null?
list
memq
member
not
+
-
*
/
=
>
>=
<=
<
abs
remainder
integer?
sqrt
eq?)
(procedure
(n)
((cond ((= n 1) 1) (else (* n (factorial (- n 1))))))
#1#)
(procedure (x) ((* x x)) #1#)
40
#f
#t
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #>)
(primitive #=>)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)
(primitive #)))
>
Here fact and square are bound to the lists:
(procedure
(n)
((cond ((= n 1) 1) (else (* n (factorial (- n 1))))))
#1#)
(procedure (x) ((* x x)) #1#)
, respectively.
The procedure bodies embedded in these (mceval) lists are source code
for mceval. In mceval, every time factorial is called the body
of factorial must be reanalyzed before it can be evaluated. To see this
let's work in mceval and aneval side-by-side. Do an
execute in mceval and then in the interaction window do:
metacircular-evaluator-loaded
> (require-library "trace.ss")
> (trace eval apply)
Now do an
execute in aneval and then in the interaction window do:
metacircular-evaluator-loaded
analyzing-metacircular-evaluator-loaded
> (require-library "trace.ss")
> (trace eval apply analyze execute-application analyze-application)
We will be tracing a lot more functions in aneval to be fair. Thus,
you might expect more lines of output from the aneval trace. But
watch what happens. Now in each interaction window alternately
define and run:
(define (square x) (* x x))
(define (factorial n)
(cond
((= n 1) 1)
(else (* n (factorial (- n 1))))
)
)
(square 5)
(factorial 5)
I got 763 lines in the aneval trace and 5668 lines in the mceval trace.
The environment takes about 47 lines each time it is printed.
I tried cutting out all but about 4 lines of the environment in each trace.
Then I had 269 lines left in the aneval trace and 1349 lines left in the
mceval trace. mceval has something like 5 times the overhead in doing
this set of computations. That does NOT mean that an aneval
execution of factorial will be five times faster than an mceval
execution of factorial, because a significant
part of the computation time is taken by the actual operations, like
multipying which is the same for both both executions. But it is likely
that an aneval execution will run faster. It will be a lot faster
if a process consists of very simple computations and lots of syntactic
analysis. It will be just a little faster if time of the actual calculations
is high in proportion to the amount of syntax analysis (as is the case
for factorial applied to large numbers).
Look up time in the help desk and click on 'time, machine' to read
about current-process-milliseconds. One should not bet too much on
these timings, but properly interpreted, they can give us useful
information. We can get some timings by altering our driver-loop:
(define (driver-loop)
(prompt-for-input input-prompt)
(let ((input (read))
(start (current-process-milliseconds)) )
(let ((output (eval input the-global-environment))
(stop (current-process-milliseconds)))
(announce-output output-prompt)
(user-print output)
(newline)
(display "elapsed time = " )
(display (- stop start))))
(driver-loop))
I changed the driver-loop in mceval for timing and wrote
it to to mcevaltime.scm. I also changed the load in aneval
to (load "mcevaltime.scm") and wrote it as anevaltime.scm.
Try running each on the definition of factorial and
(factorial 200) about three times each. Note the running times.
Here are some typical runs with anevaltime:
Welcome to DrScheme, version 100.
Language: MrEd Debug.
metacircular-evaluator-loaded
analyzing-metacircular-evaluator-loaded
> (driver-loop)
;;; M-Eval input:
(define (factorial n)
(cond
((= n 1) 1)
(else (* n (factorial (- n 1))))
)
)
;;; M-Eval value:
ok
elapsed time = 0
;;; M-Eval input:
(factorial 7)
;;; M-Eval value:
5040
elapsed time = 10
;;; M-Eval input:
(factorial 200)
;;; M-Eval value:
788657867364790503552363213932185062295135977687173263294742533244359449963403342920304284011984623904177212138919638830257642790242637105061926624952829931113462857270763317237396988943922445621451664240254033291864131227428294853277524242407573903240321257405579568660226031904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000
elapsed time = 130
Here are the same with mcevaltime:
Welcome to DrScheme, version 100.
Language: MrEd Debug.
metacircular-evaluator-loaded
> (driver-loop)
;;; M-Eval input:
(define (factorial n)
(cond
((= n 1) 1)
(else (* n (factorial (- n 1))))
)
)
;;; M-Eval value:
ok
elapsed time = 0
;;; M-Eval input:
(factorial 7)
;;; M-Eval value:
5040
elapsed time = 10
;;; M-Eval input:
(factorial 200)
;;; M-Eval value:
788657867364790503552363213932185062295135977687173263294742533244359449963403342920304284011984623904177212138919638830257642790242637105061926624952829931113462857270763317237396988943922445621451664240254033291864131227428294853277524242407573903240321257405579568660226031904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000
elapsed time = 260
Now I want you to take a look at a very interesting function.
Mathematicians like the successor function a lot. In some sense,
a great deal of mathematics can be built from recursion and the
successor function. Here we will use successor and predecessor
to build an interesting function first discovered last century
by Ackermann. Put this in a drscheme definition window.
(define pred
(lambda (x)
(- x 1)))
(define succ
(lambda (x)
(+ x 1)))
(define (A z x y)
(cond
((= z 0)
(if (= x 0)
y
(succ (A 0 (pred x) y))))
((= x 0)
(if (= z 1)
0
1))
(else
(A (pred z) (A z (pred x) y) y))))
(require-library "trace.ss")
This is only for natural numbers, so x, y, z should always be non-negative.
Now try a couple of evaluations for small values of z (<= 2) and small
x and y (<= 10).
- Suppose you define a(x, y) = (A 0 x y). What is a(x, y) ? You know
it under another name.
- Suppose you define m(x, y) = (A 1 x y). What is m(x, y) ? You know
it under another name.
- Suppose you define e(x, y) = (A 2 x y). What is e(x, y) ? You know
it under another name.
- You probably don't know another name for (A 3 x y). But can you
describe it in terms of x and y? THINK.
Now put trace on pred and succ. With trace on, try evaluations of
(A 1 4 5) and (A 2 3 4) in ordinary drscheme. Think about what you get.
Then contrast the results you get when you evaluate (A 2 7 2) in
the driver-loop of mcevaltime a couple of times and in
anevaltime a couple of times. Analyzing first is good!