;; BIG FAT DISCLAIMER: PROFESSIONAL TA; CLASS ROOM USE ONLY; DO NOT ATTEMPT! ;; ;; Speed comparisons between iteration and recursion. ;; ;; This file demonstrates the differences in running time between ;; iteration and recursion. While it will show that iteration is ;; indeed faster, it is NOT an encouragment to write iterative ;; procedures: ;; ;; - with iteration the code becomes much less clear: it is therefore ;; harder for other (not to mention yourself) to understand your code ;; and reason that it is correct. ;; - it is very hard to predict what will actually be faster and how ;; much. You should therefore not speed-optimize your code without ;; actually measuring the time differences. This is far beyond the ;; purpose of this class. ;; - the running time of a procedure should only be a concern if your ;; program spends a considerable amount of time in that procedure. ;; Otherwise your optimizations are a lot of extra work with only ;; the negative gain of obscuring the readability of your code. ;; ;; SO STUDY THIS FILE FOR YOUR ENJOYMENT, BUT KEEP PROGRAMMING RECURSIVELY. ;; ;; This file is organized with a number of examples followed by some ;; routines to time the functions. Finally, there is some example output. ;; ;; Acknowledgements: A special thank to Alan Bawden for discussions ;; on the results and how to improve them. ;; EXAMPLES OF RECURSIVE AND ITERATIVE IMPLEMENTATIONS OF DEEP-REVERSE. ;; John's recursive definition of deep-reverse. This is the easiest ;; piece of code to understand and reason about. (define (deep-reverse-0 l) (if (not (pair? L)) L (append (deep-reverse-0 (cdr L)) (list (deep-reverse-0 (car L)))))) ;; The solution has the problem that the repeated use of append ;; creates a lot of garbage since the start of the list is copied. ;; This solution avoids that by creating a higher-order procedure so ;; we can append in constant time. This could fairly easily be made ;; even clearer by introducing appropriate abstractions. (define (deep-reverse-1 l) (if (not (pair? L)) L ((deep-reverse-1-internal l) '()))) (define (deep-reverse-1-internal l) (if (null? l) (lambda (tail) tail) (lambda (tail) ((deep-reverse-1-internal (cdr L)) (cons (deep-reverse-1 (car L)) tail))))) ;; A solution due to Alan: it uses reverse directly instead of mixing ;; and matching. (This does not solve the original problem because it is ;; not a modification of reverse.) (define (deep-reverse-2 l) (if (pair? L) (reverse (map deep-reverse-2 L)) l)) ;; The same solution, but optimized to avoid producing garbage by using the ;; destructive reverse!. The use of reverse! is completely risk free HERE ;; because the list is newly produced by map. (define (deep-reverse-3 l) (if (pair? L) (reverse! (map deep-reverse-3 L)) l)) ;; Here follows various example of iterative implementations of deep-reverse. ;; Peter's original version (define (deep-reverse-iter-0 l) (define (iter l answer) (if (null? l) answer (iter (cdr l) (cons (deep-reverse-iter-0 (car l)) answer)))) (if (pair? l) (iter l '()) l)) ;; Alan's version of the same code; it is faster under interpretation because ;; the code has been moved outside. Under compilation, this change ;; does not have any effect. (define (deep-reverse-iter-1 l) (if (pair? l) (iter-1 l '()) l)) (define (iter-1 l answer) (if (null? l) answer (iter-1 (cdr l) (cons (deep-reverse-iter-1 (car l)) answer)))) ;; Another iterative version due to Alan. It is faster than the ;; student solutions under interpretation as the iterative function is ;; defined non-locally. Under compilation there is not any difference. (define (deep-reverse-iter-2 lst) (iter-2 lst '())) (define (iter-2 items new) (if (null? items) new (iter-2 (cdr items) (cons (if (pair? (car items)) (deep-reverse-iter-2 (car items)) (car items)) new)))) ;; Student solution #1. ;; The solution is hard to read, but is faster. ;; This is in part by being iterative and in part by a ``hand ;; optimization'' (whether intentionally or not) which avoids one ;; procedure call at the bottom of each subtree by testing the car. ;; Under interpretation this hand optimization gains speed, but not ;; when compiling. (define (deep-reverse-iter-3 lst) (define (iter items new) (if (null? items) new (iter (cdr items) (cons (if (pair? (car items)) (deep-reverse-iter-3 (car items)) (car items)) new)))) (iter lst '())) ;; Peter's improvement of Student solution #1 ;; This becomes faster under intrpretation because the locally defined ;; iterative function is moved outside. Under compilation there is no ;; difference. (define (iter-4 items new) (if (null? items) new (iter-4 (cdr items) (cons (if (pair? (car items)) (deep-reverse-iter-4 (car items)) (car items)) new)))) (define (deep-reverse-iter-4 lst) (iter-4 lst '())) ;; Student solution #2 ;; Like the other student solution is faster due to being iterative ;; and because of a hand optimization at the bottom of the tree. (define (deep-reverse-iter-5 lst) (define (iter in out) (cond ((null? in) out) ((not (pair? (car in))) (iter (cdr in) (cons (car in) out))) (else (iter (cdr in) (cons (deep-reverse-iter-5 (car in)) out))))) (iter lst '())) ;; TIMING FUNCTIONS ;; Here comes the functions to time the functions: We have build-big ;; to big a huge tree, repeat to repeat a computation a number of ;; times, and timing to measure the time spent. ;; random-flot and random-int returns a random float or integer, resp. (define (random-float) (flo:random-unit *random-state*)) (define (random-int) (random 1000000)) ;; (build-big d n s random) builds a tree with random numbers at the ;; leaves list structure that we can work on. It will have depth d ;; with n^{2^i} lists in each layer (top layer is layer 0); the bottom ;; layer has s elements in the list. (define (build-big d n s random) (if (= d 0) ;; Bottom layer (map (lambda (e) (random)) (make-list s)) ;; Build a layer from the previous layer. (map (lambda (e) (build-big (- d 1) (* n n) s random)) (make-list n)))) ;; (repeat n thk) repeats the computaion in thunk thk n times (define (repeat n thk) (reduce (lambda (p t) (thk)) 1 (make-list n))) ;; (timing n thk) times n invocations of the thunk thk: It prints 3 ;; numbers: the time spend on running the program, time spent during ;; garbage collection, and total time spend on running and garbage ;; collection. It is essentially taken from the MIT Scheme reference ;; manual. (define (timing n thk) (with-timings (lambda () (repeat n thk)) (lambda (run-time gc-time real-time) (write (internal-time/ticks->seconds run-time)) (write-char #\space) (write (internal-time/ticks->seconds gc-time)) (write-char #\space) (write (internal-time/ticks->seconds (+ gc-time run-time))) (newline)))) ;; A big data structure; Approximately 65.000 elements (define tree-64K-depth-3 (build-big 3 3 30 random-float)) ;; A thunk that does deepreverse. (define (thunk-deepreverse deep-reverse) (lambda ()(begin (deep-reverse tree-64K-depth-3) '()))) ;; The following makes the compiled code faster; see the MIT Scheme manual ;; for details. (declare (usual-integrations)) ;; EXAMPLE OUTPUT ;; We run the test in 4 ways: ;; - interpreted or compiled---the former is what you use in class, ;; the latter is what you would do in real-life. ;; ;; In both cases we use the -large command line option to allow ;; our data structure to be build. We use the following command to ;; compile: ;; ;; After that the compiled procedure can be loaded using ;; (load "timing"). ;; ;; - the procedures called in the same session or loaded independently ;; into a clean session---the former ensures that the procedures ;; work on the exact same data structure (not that the differences ;; in the leaves should make any difference); the latter ensures ;; that a procedure is not punished by the garbage produced by a ;; previous procedure. ;; ;; The test results are as follows; the full sessions are shown below. ;; 1) When interpreting: ;; -- One session --- -- Separate sessions - ;; Run GC Total Run GC Total ;; deep-reverse: 5.68 1.37 7.05 5.71 1.49 7.2 ;; deep-reverse-1: 5.13 .53 5.66 5.29 .6 5.89 ;; (deep-reverse-2: 1.09 .26 1.35 1.13 .25 1.38) ;; (deep-reverse-3: 1.1 .14 1.24 1.22 .11 1.33) ;; deep-reverse-iter-0 5.64 .63 6.27 5.47 .59 6.06 ;; deep-reverse-iter-1: 3.98 .28 4.26 3.9 .37 4.27 ;; deep-reverse-iter-2: 3.93 .29 4.22 4.06 .26 4.32 ;; deep-reverse-iter-3: 5.03 .23 5.26 4.73 .23 4.96 ;; deep-reverse-iter-4: 3.9 .28 4.18 3.87 .26 4.13 ;; deep-reverse-iter-5: 5.24 .22 5.46 5.22 .2 5.42 ;; ;; From these results we notice a couple of things: ;; ;; - while by far the easiest to read, the recursive procedure is 71% ;; slower than the fastest solution and 39% slower than the best ;; student solution. Put differently, there is a ;; 42% gain in using the fastest implementation, and a 28% gain by ;; using the best student solution. ;; ;; The cost is incurred by the the append which produces a number of ;; temporary lists which we discard. ;; ;; - avoiding the append with deep-reverse-1 does give a speed-up; ;; readibility is lowered though. ;; - using map and reverse beats everything, but that most likely has ;; to do with the fact that map and reverse are compiled in and ;; therefore faster. ;; - a notable gain (about 19%) is obtained by making the procedures ;; global. ;; - similarly, the ``hand optimization'' gives a gain of about 18%. ;; - some solutions run faster, some run slower when run in a separate ;; session. Consequently, there does not seem to be much influence ;; from one test to another. ;; ;; 2) When compiling: ;; -- One session --- -- Separate sessions - ;; Run GC Total Run GC Total ;; deep-reverse-0: .64 1.34 1.98 .68 1.27 1.95 ;; deep-reverse-1: .41 .38 .79 .35 .43 .78 ;; deep-reverse-2: .1 .19 .29 .12. .24 .36 ;; deep-reverse-3: .11 .09 .2 .1 .09 .19 ;; deep-reverse-iter-0 .08 .09 .17 .08 .09 .17 ;; deep-reverse-iter-1: .06 .09 .15 .06 .11 .17 ;; deep-reverse-iter-2: .09 .08 .17 .08 .08 .16 ;; deep-reverse-iter-3: .06 .08 .14 .09 .09 .18 ;; deep-reverse-iter-4: .07 .09 .16 .08 .09 .17 ;; deep-reverse-iter-5: .05 .1 .15 .09 .08 .17 ;; ;; From these results we notice a couple of things: ;; ;; - The straight-forward recursive procedure is now an order of ;; magnitude slower than the iterative procedure (12 times slower in ;; precise numbers): it is 12 times as slow as the best iterative. ;; - avoding the append through higher-order procedures buy us a factor 2, ;; but not more because we create a lot of temporary procedures. ;; - using map and reverse internally we are pretty much at par with ;; the best iterative. The single reverse! is a very clean way getting ;; the optimal speed and a very clean solution. ;; - all the iterative procedures take the same amount of time to ;; run. There is no need to hand-optimize to save call or to move ;; the local definition outside. ;; - Again, the separate session vs. individual session does not seem ;; to have any real effect. ;; ;; 1) The full session of intepreting the code and running the procedures in ;; one session: ;; ;; pan:~ > scheme -large ;; Scheme Microcode Version 14.8 ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-0)) ;; 5.68 1.37 7.05 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-1)) ;; 5.13 .53 5.66 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-2)) ;; 1.09 .26 1.35 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-3)) ;; 1.1 .14 1.24 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-0)) ;; 5.64 .63 6.27 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-1)) ;; 3.98 .28 4.26 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-2)) ;; 3.93 .29 4.22 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-3)) ;; 5.03 .23 5.26 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-4)) ;; 3.9 .28 4.18 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-5)) ;; 5.24 .22 5.46 ;; ;Value: () ;; ;; 2) The full session of intepreting the code and running the procedures in ;; seperate sessions: ;; ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse)) ;; 5.71 1.49 7.2 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~/<1>cosi21b/set2 > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-1)) ;; 5.29 .6 5.89 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~/<1>cosi21b/set2 > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-2)) ;; 1.13 .25 1.38 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~/<1>cosi21b/set2 > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-3)) ;; 1.22 .11 1.33 ;; ;Value: () ;; ;; 1 ]=> ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-0)) ;; 5.47 .59 6.06 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [..] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-1)) ;; 3.9 .37 4.27 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-2)) ;; 4.06 .26 4.32 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-3)) ;; 4.73 .23 4.96 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; ;; ;Loading "timing.scm" -- done ;; ;Value: thunk-deepreverse ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-4)) ;; 3.87 .26 4.13 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing.scm") ;; ;; ;Loading "timing.scm" -- done ;; ;Value: thunk-deepreverse ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-5)) ;; 5.22 .2 5.42 ;; ;Value: () ;; ;; ;; 3) The full session of intepreting the code and running the procedures in ;; one session: ;; ;; pan:~ > scheme -large -compiler ;; [...] ;; 1 ]=> (cf "timing.scm") ;; [...] ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; ;; ;Loading "timing.com" -- done ;; ;Value: thunk-deepreverse ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse)) ;; .64 1.34 1.98 ;; ;Value: () ;; ;; 1 ]=> (load "timing") ;; ;; ;Loading "timing.com" -- done ;; ;Value: thunk-deepreverse ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-1)) ;; .41 .38 .79 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-2)) ;; .1 .19 .29 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-3)) ;; .11 .09 .2 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-0)) ;; .08 .09 .17 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-1)) ;; .06 .09 .15 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-2)) ;; .09 .08 .17 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-3)) ;; .06 .08 .14 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-4)) ;; .07 .09 .16 ;; ;Value: () ;; ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-5)) ;; .05 .1 .15 ;; ;Value: () ;; ;; ;; ;; 4) The full session of compiling the code and running the procedures in ;; seperate sessions: ;; ;; pan:~ > scheme -large -compiler ;; [...] ;; 1 ]=> (cf "timing.scm") ;; [...] ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-0)) ;; .68 1.27 1.95 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~/<1>cosi21b/set2 > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-1)) ;; .35 .43 .78 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~/<1>cosi21b/set2 > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-2)) ;; .12 .24 .36 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~/<1>cosi21b/set2 > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-3)) ;; .1 .09 .19 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-0)) ;; .08 .09 .17 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-1)) ;; .06 .11 .17 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-2)) ;; .08 .08 .16 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-3)) ;; .09 .09 .18 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-4)) ;; .08 .09 .17 ;; ;Value: () ;; ;; 1 ]=> (exit) ;; ;; ;; Kill Scheme (y or n)? y ;; Yes ;; Happy Happy Joy Joy. ;; pan:~ > scheme -large ;; [...] ;; 1 ]=> (load "timing") ;; [...] ;; 1 ]=> (timing 10 (thunk-deepreverse deep-reverse-iter-5)) ;; .09 .08 .17 ;; ;Value: ()