[racket-dev] Expansion of optional arguments in lambda

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Sun Feb 24 14:14:40 EST 2013

At Sun, 24 Feb 2013 09:51:12 -0800, Eric Dobson wrote:
> lambda supports optional arguments, and does this by expanding out into a
> core form that has flag arguments for if each argument is supplied. This is
> tricky to type in TR and so I was investigating why it did it this way. I
> did a micro benchmark on another method of expansion and it was 60% faster.
> Is there a reason that racket does it the current way that I am missing.
> #lang racket
> (define f
>   (case-lambda
>     (() (f 1))
>     ((a) (f a (+ 3 a)))
>     ((a b) (f a b (* a b)))
>     ((a b c) (f a b c (- a (/ c b))))
>     ((a b c d) (+ a b c d))))
> (define (g (a 1) (b (+ 3 a)) (c (* a b)) (d (- a (/ c b))))
>   (+ a b c d))

I'll have to investigate more, but I don't think the story is simple.

Below are the decompiled forms, and you'll see that they're essentially
the same, due to inlining and other compiler optimizations.

Part of the answer is probably how the different forms interact with
inlining in the timing loops. If I change the code to

 (define-values (f g)
   (let ()
     (define f ....)
     (define g ....)
     (values f g)))

 (set! f f)
 (set! g g)

after the definition of `g', then performance is the same for the `f'
and `g' (with a tiny difference that may be due to the order of clauses
in `f' and `g').

You may also want to look at performance in the case that the eventual
body cannot be inlined (e.g., because it's too large). Then, I think the
chain of applications for `f' will probably be significantly worse than
the more direct implementation of `g'.


       '#(/private/tmp/c.rkt:5:4 #<path:/private/tmp/c.rkt> 5 4 44 10 #t)
       '(flags: preserves-marks single-result)
       '#(/private/tmp/c.rkt:6:4 #<path:/private/tmp/c.rkt> 6 4 59 19 #t)
       '(flags: preserves-marks single-result)
       (let ((local13 (+ '3 arg0-12)))
         (let ((local16 (* arg0-12 local13)))
           (+ arg0-12 local13 local16 (- arg0-12 (/ local16 local13))))))
      ((arg0-27 arg1-28)
       '#(/private/tmp/c.rkt:7:4 #<path:/private/tmp/c.rkt> 7 4 83 23 #t)
       '(flags: preserves-marks single-result)
       (let ((local29 (* arg0-27 arg1-28)))
         (+ arg0-27 arg1-28 local29 (- arg0-27 (/ local29 arg1-28)))))
      ((arg0-40 arg1-41 arg2-42)
       '#(/private/tmp/c.rkt:8:4 #<path:/private/tmp/c.rkt> 8 4 111 33 #t)
       '(flags: preserves-marks single-result)
       (+ arg0-40 arg1-41 arg2-42 (- arg0-40 (/ arg2-42 arg1-41))))
      ((arg0-51 arg1-52 arg2-53 arg3-54)
       '#(/private/tmp/c.rkt:9:4 #<path:/private/tmp/c.rkt> 9 4 149 23 #t)
       '(flags: preserves-marks single-result)
       (+ arg0-51 arg1-52 arg2-53 arg3-54))))

      (() '(flags: preserves-marks single-result) '9)
      ((arg0-59 arg1-60 arg2-61 arg3-62)
       '(flags: preserves-marks single-result)
       (+ arg0-59 arg1-60 arg2-61 arg3-62))
      ((arg0-67 arg1-68 arg2-69)
       '(flags: preserves-marks single-result)
       (+ arg0-67 arg1-68 arg2-69 (- arg0-67 (/ arg2-69 arg1-68))))
      ((arg0-78 arg1-79)
       '(flags: preserves-marks single-result)
       (let ((local80 (* arg0-78 arg1-79)))
         (+ arg0-78 arg1-79 local80 (- arg0-78 (/ local80 arg1-79)))))
       '(flags: preserves-marks single-result)
       (let ((local92 (+ '3 arg0-91)))
         (let ((local95 (* arg0-91 local92)))
           (+ arg0-91 local92 local95 (- arg0-91 (/ local95 local92)))))))))

Posted on the dev mailing list.