This post looks at how the performance of deeply recursive procedures can be improved in
Motto.
Let us start with the simple implementation of a procedure to calculate Fibonacci numbers:
> [def n n 0 eq ? 0 [n 1 eq ? 1 [n 1 - fib n 2 - fib +]]] def fib
> 5 fib
5
> 10 fib
55
> 20 fib
6765
20 fib makes more than 20000 recursive calls. On my machine, It took almost 700 milliseconds to run.
There are three ways to make this run faster.
Iteration
A dramatic improvement in speed can be achieved by using iteration:
> [def n
0 def a
1 def b
0 def i
[i n lt] [a def tmp
b set a
b tmp + set b
i 1 + set i] loop
a] def fib
> [20 fib] time!
2 ms real time
...
6765
Memoization
If the algorithm is not amenable to iteration, we can cache the results. Subsequent calls can
return the result from the cache and avoid a costly re-computation. Here we optimize the fib
procedure by storing all computed Fibonacci numbers in a map.
> mk-map def fib-memo
> [-1 swap fib-memo map-get] def get-fib
> [fib-memo map-put] def set-fib
> 0 0 set-fib
> 1 1 set-fib
> [def n
n get-fib -1 eq ?
[n n 1 - fib n 2 - fib + set-fib n get-fib]
[n get-fib]] def fib
;; tests.
> [20 fib] time!
3 ms real time
...
> [20 fib] time!
0 ms real time
...
The number of recursive calls came down to 38 for the first instance and there were none for the second.
Of course, the cache takes up additional memory and that is the price paid for the increased speed.
Virtual Machine definitions
If you want a fast recursive procedure, without trading memory, you can add fib as a Virtual Machine
definition. That means you have to write the procedure in Scheme and make it callable from Motto:
> (define (fib n)
(cond
((= n 0) 0)
((= n 1) 1)
(else
(+ (fib (- n 1)) (fib (- n 2))))))
> ;; Make `fib` accessible to Motto programs.
> (def 'fib
(lambda (vm stack)
(stack-push (stack-pop stack) (fib (stack-top stack)))))
> [20 fib] time!
23 ms real time
...
If you have programmed in Java, you can think of this as making a safe JNI call, i.e, calling
a fast low-level routine without the risks of segmentation faults, VM crashes etc.
The implementation of Motto require some improvements to make recursive calls more efficient.
Having a mutable data stack will help a lot. The entire VM, or at least parts of it should
be reimplemented in a systems language. But its another project, another story!