I am trying to encode a small lambda calculus with algebraic datatypes in Scheme. I want it to use lazy evaluation, for which I tried to use the primitives delay and force. However, this has a large negative impact on the performance of evaluation: the execution time on a small test case goes up by a factor of 20x.
While I did not expect laziness to speed up this particular test case, I did not expect a huge slowdown either. My question is thus: What is causing this huge overhead with lazy evaluation, and how can I avoid this problem while still getting lazy evaluation? I would already be happy to get within 2x the execution time of the strict version, but faster is of course always better.
Below are the strict and lazy versions of the test case I used. The test deals with natural numbers in unary notation: it constructs a sequence of 2^24 sucs followed by a zero and then destructs the result again. The lazy version was constructed from the strict version by adding delay and force in appropriate places, and adding let-bindings to avoid forcing an argument more than once. (I also tried a version where zero and suc were strict but other functions were lazy, but this was even slower than the fully lazy version so I omitted it here.)
I compiled both programs using compile-file in Chez Scheme 9.5 and executed the resulting .so files with petite --program. Execution time (user only) for the strict version was 0.578s, while the lazy version takes 11,891s, which is almost exactly 20x slower.
Strict version
(define zero 'zero)
(define (suc x) (cons 'suc x))
(define one (suc zero))
(define two (suc one))
(define three (suc two))
(define (twice m)
(if (eq? m zero)
zero
(suc (suc (twice (cdr m))))))
(define (pow2 m)
(if (eq? m zero)
one
(twice (pow2 (cdr m)))))
(define (consume m)
(if (eq? m zero)
zero
(consume (cdr m))))
(consume (pow2 (twice (twice (twice three)))))
Lazy version
(define zero (delay 'zero))
(define (suc x) (delay (cons 'suc x)))
(define one (suc zero))
(define two (suc one))
(define three (suc two))
(define (twice m)
(delay
(let ((mv (force m)))
(if (eq? mv 'zero)
(force zero)
(force (suc (suc (twice (cdr mv)))))))))
(define (pow2 m)
(delay
(let ((mv (force m)))
(if (eq? mv 'zero)
(force one)
(force (twice (pow2 (cdr mv))))))))
(define (consume m)
(delay
(let ((mv (force m)))
(if (eq? mv 'zero)
(force zero)
(force (consume (cdr mv)))))))
(force (consume (pow2 (twice (twice (twice three))))))