let-syntax costs 1ms?

31 views
Skip to first unread message

William J. Bowman

unread,
Mar 10, 2022, 11:50:23 PM3/10/22
to Racket Developers
I've extracted a piece of a language implementation here (also attached)
https://gist.github.com/wilbowma/87d7e18718e08968cc4b2d003efbff2b

It provides two implementations of a little language that provides an unbounded
number of implicit global mutable variables.
I decided to bind the first 1000 of them because I figure no user would generate
that many, instead of doing anything clever.

One method uses `let-syntax` and implements these variables with set!
transformers, and the other just binds the variables with `let`.

It seems the version that uses `let-syntax` runs in time directly proportional
to the number of these variables I decide to bind, i.e., the number of macros I
introduce via `let-syntax`; about 1ms per variable bound, regardless of whether
the macros are ever used.

This behaviour surprised me a lot. Is this expected?

--
William J. Bowman
meow.rkt

William J. Bowman

unread,
Mar 11, 2022, 2:57:23 AM3/11/22
to Racket Developers
shhyou helped me figure this out; it was really my fault, as I was generating a new set!-transformer each time through the loop, instead of a call to a single transformer.

https://gist.github.com/shhyou/ff5366e8469d4da8f54d262a52744f30#file-bind-it-rkt

Fell in a standard macro trap.

--
William J. Bowman
> --
> You received this message because you are subscribed to the Google Groups "Racket Developers" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-dev/YirU6gt5MxSnLcvf%40williamjbowman.com.

> #lang racket
> (require (for-syntax racket/syntax))
>
> #|
> 1,000 fvars; let-syntax implementation
> cpu time: 1162 real time: 1165 gc time: 156
>
> 10,000 fvars; let-syntax implementation
> cpu time: 10928 real time: 10964 gc time: 1374
>
> 1,000 fvars; let implementation
> cpu time: 20 real time: 20 gc time: 3
>
> 10,000 fvars; let implementation
> cpu time: 225 real time: 225 gc time: 54
> |#
>
> (begin-for-syntax
> (define current-fvars (make-parameter 10000))
>
> (define (bind-fvars s n tail)
> #`(let-syntax
> #,(for/list ([i (in-range 0 n)])
> (with-syntax ([fvar (syntax-local-introduce (format-id #f "fv~a" i))]
> [offset i]
> [stack s])
> #`[fvar (make-set!-transformer
> (lambda (stx)
> (syntax-case stx ()
> [(set! id v)
> #`(vector-set! stack offset v)]
> [id (identifier? #'id)
> #`(vector-ref stack offset)])))]))
> #,tail)))
>
> (define-syntax (my-module stx)
> (syntax-case stx ()
> [(_ e ...)
> (with-syntax ([s #'stack])
> #`(let ([s (make-vector #,(current-fvars) (void))])
> #,(bind-fvars #'s (current-fvars) #`(begin e ...))))]))
>
> (define-namespace-anchor a)
>
> (displayln "1,000 fvars; let-syntax implementation")
>
> (time
> (eval
> '(begin
> (begin-for-syntax
> (current-fvars 1000))
> (my-module
> (set! fv0 8)
> (set! fv1 8)
> (+ fv0 fv1)))
> (namespace-anchor->namespace a)))
>
> (displayln "10,000 fvars; let-syntax implementation")
>
> (time
> (eval
> '(begin
> (begin-for-syntax
> (current-fvars 10000))
> (my-module
> (set! fv0 8)
> (set! fv1 8)
> (+ fv0 fv1)))
> (namespace-anchor->namespace a)))
>
> (define-for-syntax (bind-fvars2 n tail)
> #`(let #,(for/list ([i (in-range 0 n)])
> (with-syntax ([fvar (syntax-local-introduce (format-id #f "fv~a" i))])
> #`[fvar (void)]))
> #,tail))
>
> (define-syntax (my-module2 stx)
> (syntax-case stx ()
> [(_ e ...)
> (bind-fvars2 (current-fvars) #`(begin e ...))]))
>
> ;; expansion time increases with the number of let bindings, but not nearly as bad
> ;; expansion time seems to be 1ms per fvar, i.e., per let-syntax?
>
> (displayln "1,000 fvars; let implementation")
>
> (time
> (eval
> '(begin
> (begin-for-syntax
> (current-fvars 1000))
> (my-module2
> (set! fv0 8)
> (set! fv1 8)
> (+ fv0 fv1)))
> (namespace-anchor->namespace a)))
>
> (displayln "10,000 fvars; let implementation")
> (time
> (eval
> '(begin
> (begin-for-syntax
> (current-fvars 10000))
> (my-module2
> (set! fv0 8)
> (set! fv1 8)
> (+ fv0 fv1)))
> (namespace-anchor->namespace a)))

Sorawee Porncharoenwase

unread,
Mar 11, 2022, 3:35:36 AM3/11/22
to William J. Bowman, Racket Developers

Off-topic, but FWIW, you need set! in the list of literals for syntax-case. Otherwise, you could have a spurious match.

(define-syntax test
  (make-set!-transformer
   (λ (stx)
     (syntax-case stx ()
       [(set! id v)
        #'(println '(set! id v))]))))

(test 1 2) ;=> '(test 1 2)
(set! test 3) ;=> '(set! test 3)

(define-syntax test2
  (make-set!-transformer
   (λ (stx)
     (syntax-case stx (set!)
       [(set! id v)
        #'(println '(set! id v))]))))

#;(test2 1 2) ;=> bad syntax
(set! test2 3) ;=> '(set! test2 3)


Ryan Culpepper

unread,
Mar 11, 2022, 3:51:40 AM3/11/22
to Sorawee Porncharoenwase, William J. Bowman, Racket Developers
See make-variable-like-transformer in the syntax/transformer library. It's a more general version of your delegate-fvar.

Ryan


William J. Bowman

unread,
Mar 11, 2022, 1:26:23 PM3/11/22
to ry...@racket-lang.org, Sorawee Porncharoenwase, Racket Developers
Reply all
Reply to author
Forward
0 new messages