Skip to content

Instantly share code, notes, and snippets.

@jackfirth
Last active November 8, 2023 05:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jackfirth/3842bdbe751ee9c6f1f03c39e912bd33 to your computer and use it in GitHub Desktop.
Save jackfirth/3842bdbe751ee9c6f1f03c39e912bd33 to your computer and use it in GitHub Desktop.
Measuring the performance of various ways of dispatching on the type of something in Racket
#lang racket
(require racket/generic)
(define-generics vable
(get-v vable))
(struct a (v)
#:methods gen:vable
[(define (get-v a)
(a-v a))])
(struct b (v)
#:methods gen:vable
[(define (get-v b)
(b-v b))])
(struct c (v)
#:methods gen:vable
[(define (get-v c)
(c-v c))])
(define a-inst (a 1))
(define b-inst (b 2))
(define c-inst (c 3))
(define instances (vector a-inst b-inst c-inst))
(define ticks 1000)
(define subticks 10000)
(define (pick-struct)
(vector-ref instances (random 3)))
(collect-garbage)
(collect-garbage)
(collect-garbage)
(displayln "destructuring match:")
(time
(void
(for ([_ (in-range ticks)])
(define s (pick-struct))
(for ([_ (in-range subticks)])
(match s
[(a v) v]
[(b v) v]
[(c v) v])))))
(newline)
(collect-garbage)
(collect-garbage)
(collect-garbage)
(displayln "cond with predicates:")
(time
(void
(for ([_ (in-range ticks)])
(define s (pick-struct))
(for ([_ (in-range subticks)])
(cond
[(a? s) (a-v s)]
[(b? s) (b-v s)]
[else (c-v s)])))))
(newline)
(collect-garbage)
(collect-garbage)
(collect-garbage)
(displayln "match with predicates:")
(time
(void
(for ([_ (in-range ticks)])
(define s (pick-struct))
(for ([_ (in-range subticks)])
(match s
[(? a?) (a-v s)]
[(? b?) (b-v s)]
[_ (c-v s)])))))
(newline)
(collect-garbage)
(collect-garbage)
(collect-garbage)
(displayln "generic interface:")
(time
(void
(for ([_ (in-range ticks)])
(define s (pick-struct))
(for ([_ (in-range subticks)])
(get-v s)))))

I get these results using racket 8.10 in DrRacket on my machine:

destructuring match:
cpu time: 67 real time: 71 gc time: 2

cond with predicates:
cpu time: 1131 real time: 1151 gc time: 8

match with predicates:
cpu time: 790 real time: 791 gc time: 5

generic interface:
cpu time: 946 real time: 960 gc time: 5

If I make all of the structs #:transparent, I get these results in DrRacket:

destructuring match:
cpu time: 63 real time: 71 gc time: 2

cond with predicates:
cpu time: 413 real time: 438 gc time: 5

match with predicates:
cpu time: 262 real time: 283 gc time: 4

generic interface:
cpu time: 398 real time: 424 gc time: 4

If I make them all #:authentic, I get this in DrRacket:

destructuring match:
cpu time: 59 real time: 68 gc time: 2

cond with predicates:
cpu time: 1126 real time: 1170 gc time: 7

match with predicates:
cpu time: 758 real time: 765 gc time: 4

generic interface:
cpu time: 937 real time: 944 gc time: 4

If I make them all #:transparent and #:authentic, I get this in DrRacket:

destructuring match:
cpu time: 59 real time: 61 gc time: 2

cond with predicates:
cpu time: 1123 real time: 1184 gc time: 7

match with predicates:
cpu time: 768 real time: 775 gc time: 4

generic interface:
cpu time: 965 real time: 981 gc time: 5

Command line:

destructuring match:
cpu time: 32 real time: 33 gc time: 0

cond with predicates:
cpu time: 35 real time: 36 gc time: 0

match with predicates:
cpu time: 35 real time: 36 gc time: 0

generic interface:
cpu time: 148 real time: 151 gc time: 0

Command line with #:transparent:

destructuring match:
cpu time: 33 real time: 34 gc time: 0

cond with predicates:
cpu time: 36 real time: 37 gc time: 0

match with predicates:
cpu time: 35 real time: 36 gc time: 0

generic interface:
cpu time: 148 real time: 150 gc time: 0

Command line with #:authentic:

destructuring match:
cpu time: 23 real time: 24 gc time: 0

cond with predicates:
cpu time: 23 real time: 24 gc time: 0

match with predicates:
cpu time: 23 real time: 24 gc time: 0

generic interface:
cpu time: 149 real time: 153 gc time: 0

Command line with #:transparent and #:authentic:

destructuring match:
cpu time: 22 real time: 23 gc time: 0

cond with predicates:
cpu time: 22 real time: 23 gc time: 0

match with predicates:
cpu time: 22 real time: 23 gc time: 0

generic interface:
cpu time: 145 real time: 148 gc time: 0

In summary, in DrRacket:

  1. Destructuring pattern matches are consistently the fastest by a huge margin.
  2. Destructuring pattern matches are slightly faster on transparent structs, and slightly faster still on authentic structs.
  3. Generic interfaces are around 15x slower than destructuring, unless the struct is transparent and not authentic, in which case they're only about 6x slower.
  4. Directly calling the predicates and accessors is very slow, even compared to generic interfaces, for unclear reasons.

Some of this doesn't make much sense to me. Opinions and explanations welcome.

In the command line, things are much more as expected. All of the non-generic interface variants perform about the same, and making the struct transparent doesn't make much of a difference for them but making it authentic makes it slightly faster. Generic interfaces are about 5x slower and have no speedup on authentic or transparent structs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment