[racket] Why parameterize is so sloooow?

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Fri Aug 16 08:09:35 EDT 2013

At Fri, 16 Aug 2013 13:59:55 +0400, Roman Klochkov wrote:
> 
> I compared parameterize with lexical var
> ----
> > (require rackunit)
> > (define my-parameter (make-parameter (box 0)))
> > (time 
>    (parameterize ([my-parameter (box 0)]) 
>      (for ([x (in-range 10000000)]) 
>        (set-box! (my-parameter) 
>                  (add1 (unbox (my-parameter))))) 
>      (check-equal? (unbox (my-parameter)) 10000000))) 
> cpu time: 3578 real time: 3610 gc time: 0
> > (time 
>    (let ([my-parameter (box 0)]) 
>      (for ([x (in-range 10000000)]) 
>        (set-box! my-parameter 
>                  (add1 (unbox my-parameter)))) 
>      (check-equal? (unbox my-parameter) 10000000))) 
> cpu time: 47 real time: 47 gc time: 0
> ----
> 
> 100 times difference!
> 
> The same experiment with Common Lisp (SBCL):
> ----
> CL-USER> (setf *a* (list 0))
> (0)
> CL-USER> (time (progn (loop :for i :from 0 :below 10000000
>             :do (setf (car *a*) (+ 1 (car *a*)))) (= (car *a*) 10000000)))
> Evaluation took:
>   0.063 seconds of real time
>   0.062500 seconds of total run time (0.062500 user, 0.000000 system)
>   98.41% CPU
>   172,464,541 processor cycles
>   0 bytes consed
>   
> T
> CL-USER> (let ((a (list 0))) (time (loop :for i :from 0 :below 10000000
>             :do (setf (car a) (+ 1 (car a))))) (= (car a) 10000000))
>               
> Evaluation took:
>   0.047 seconds of real time
>   0.046875 seconds of total run time (0.046875 user, 0.000000 system)
>   100.00% CPU
>   132,098,942 processor cycles
>   0 bytes consed
>   
> T
> ----
> Only 1.5 times.
> 
> Is it undesirable to use parameterize as replacement for common lisp special 
> variables? What is it designed for then?

Parameters in Racket are grouped together in a an extra layer called a
"parameterization", which enables capture of the current values of all
parameters. For example, when a new thread is created in Racket, then
the new inherits all of the current parameter values from the creating
thread. A lack of cleverness in that layer is probably the main effect
on performance in yuor example.

Using a raw, symbol-keyed continuation mark would be closer to a Common
Lisp special variable, I think. On my machine:

 ;; parameter
 > (time 
    (parameterize ([my-parameter (box 0)]) 
      (for ([x (in-range 10000000)]) 
        (set-box! (my-parameter) 
                  (add1 (unbox (my-parameter))))) 
      (check-equal? (unbox (my-parameter)) 10000000)))
 cpu time: 2539 real time: 2537 gc time: 0

 ;; direct
 > (time 
    (let ([my-parameter (box 0)]) 
      (for ([x (in-range 10000000)]) 
        (set-box! my-parameter 
                  (add1 (unbox my-parameter)))) 
      (check-equal? (unbox my-parameter) 10000000))) 
 cpu time: 45 real time: 45 gc time: 0

 ;; raw continuation mark:
 > (time 
    (let ([my-parameter
           (lambda ()
             (continuation-mark-set-first #f 'my-parameter))])
      (with-continuation-mark
        'my-parameter 
        (box 0)
        (begin
          (for ([x (in-range 10000000)]) 
            (set-box! (my-parameter) 
                      (add1 (unbox (my-parameter))))) 
          (check-equal? (unbox (my-parameter)) 10000000)))))
 cpu time: 244 real time: 243 gc time: 0

That's still a fact of 5 difference. I expect that dynamic binding and
special variables have played a more prominent role in Common Lisp than
parameters or even continuation-mark lookup in Racket, and so it would
make sense that more work has been done in the SBCL to make them fast.



Posted on the users mailing list.