#lang racket (require racket/private/class-internal) (define-syntax (let-static stx) (syntax-case stx () [(let-static ([var rhs] ...) . body) (with-syntax ([(gvar ...) (syntax-local-lift-values-expression (length (syntax->list #'(var ...))) #'(values rhs ...))]) #'(let-syntax ([var (make-rename-transformer #'gvar)] ...) . body))])) ;; An ordinary Racket class. (define a% (class* object% () (super-new) (define/public (op x) (+ x 1)))) ;; ==== Cached versions of send ==== ;; (Real version should base cache on class, not object.) ;; send/cache0 ;; Uses 2 vars. ;; Problem: not safe for space! ;; Problem: race condition in 2 set!s. (define-syntax send/cache0 (syntax-rules () [(_ obj-expr msg arg ...) (let-static ([*memo-class* #f] [*memo-method* #f]) (let ([obj obj-expr]) (let ([f (if (eq? obj *memo-class*) *memo-method* (let-values ([(method _obj) (find-method/who 'send obj 'msg)]) (set! *memo-class* obj) (set! *memo-method* method) method))]) (f obj arg ...))))])) ;; send/cache1 ;; Uses 2 weak-boxes to be safe for space. ;; Problem: race condition in 2 set!s. (define-syntax send/cache1 (syntax-rules () ((_ obj-expr msg arg ...) (let-static ([*memo-class* (make-weak-box #f)] [*memo-method* (make-weak-box #f)]) (let* ([obj obj-expr] [memo-class (weak-box-value *memo-class*)] [memo-method (weak-box-value *memo-method*)]) (let ([f (if (eq? obj memo-class) memo-method (let-values ([(method _obj) (find-method/who 'send obj 'msg)]) (set! *memo-class* (make-weak-box obj)) (set! *memo-method* (make-weak-box method)) method))]) (f obj arg ...))))))) ;; send/cache2 ;; Uses one weak-box instead of two, so safe for space and eliminates ;; race condition. ;; Problem (minor): the weak-box's pair is held nowhere else, so the ;; cache is potentially emptied on every (major?) GC. (But that's ok.) (define-syntax send/cache2 (syntax-rules () ((_ obj-expr msg arg ...) (let-static ([*memo* (make-weak-box #f)]) (let* ([obj obj-expr] [memo (weak-box-value *memo*)] [memo-class (and (pair? memo) (car memo))] [memo-method (and (pair? memo) (cdr memo))]) (let ([f (if (eq? obj memo-class) memo-method (let-values ([(method _obj) (find-method/who 'send obj 'msg)]) (set! *memo* (make-weak-box (cons obj method))) method))]) (f obj arg ...))))))) ;; Test objects. (define a0 (new a%)) ;; Syntax: (measure-ns exp) ;; ;; Expands to an expression that repeats "exp" NREPEATS times, ;; measuring the elapsed time, and returns the number of nanoseconds ;; of CPU time used *per iteration*, excluding any GC time. (define NREPEATS 5000000) (define-syntax measure-ns (syntax-rules () ((_ exp) (call-with-values (lambda () (pretty-print `(measuring exp)) (time-apply (lambda () (do ((i 0 (+ i 1))) ((= i NREPEATS)) exp)) '())) (lambda (results cpu real gc) (/ (* 1000000000.0 (/ (- cpu gc) 1000.0)) NREPEATS)))))) ;; Main program. ;; Measure the time for a null measure-ns loop first, then measure the ;; operations of interest, subtracting the null-time overhead ;; measurement from each to get an estimate of the time taken for the ;; interesting operation. (let ((null-time (measure-ns 123))) (define (report-on t) (let ((name (first t)) (ns/op (second t))) (write (list name (- ns/op null-time))) (newline))) (for-each report-on `( ;; Report on the loop overhead for sanity checking. (constant ,null-time) ;; How long does a plain Scheme addition operation take? (simple-add ,(measure-ns (+ 123 12))) ;; How long does a regular Racket object send take? (normal-send ,(measure-ns (send a0 op 123))) ;; What about an approximation to a monomorphic inline ;; cache for the Racket object system? This should be ;; much faster than plain old send. ;; 2 vars (not SFS, race condition) (memoized-send/0 ,(measure-ns (send/cache0 a0 op 123))) ;; 2 weak-boxes (SFS, race condition) (memoized-send/1 ,(measure-ns (send/cache1 a0 op 123))) ;; 1 weak-box (SFS, no race condition) (memoized-send/2 ,(measure-ns (send/cache2 a0 op 123))) )))