#lang racket
(require racket/private/class-internal)

;; An ordinary Racket class.
(define a%
  (class* object% ()
    (super-new)
    (define/public (op x) (+ x 1))))

;; Representation of a trivial vtable.
(struct ob (vt state) #:transparent)

;; A simple vtable providing a single method named "op".
(define (b%-vt selector)
  (case selector
    ((op) (lambda (self x) (+ x 2)))
    (else (error 'dnu))))

;; A simple class, using b%-vt as its behaviour.
(define (b%)
  (ob b%-vt 'no-state))

;; An uncached send to a struct ob.
(define-syntax unmemo-send
  (syntax-rules ()
    ((_ obj msg arg ...)
     (let ((tmp obj))
       (((ob-vt tmp) 'msg) tmp arg ...)))))

;; A quasi-cached send to a struct ob.
;;
;; A real cache would have per-send-site state rather than a single
;; (!) global variable.
(define *memo-class* #f)
(define *memo-method* #f)
(define-syntax memo-send
  (syntax-rules ()
    ((_ obj msg arg ...)
     (let* ((tmp obj)
            (cls (ob-vt tmp)))
       (if (eq? *memo-class* cls)
           (*memo-method* tmp arg ...)
           (let ((method (cls 'msg)))
             (set! *memo-class* cls)
             (set! *memo-method* method)
             (method tmp arg ...)))))))

;; Test objects.
(define a0 (new a%))
(define b0 (b%))

;; 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 if we expand the send macro in place?
	      ;; This should be almost identical to the time for the
	      ;; previous expression.
	      (expanded-normal-send
	       ,(measure-ns (let-values (((temp1) 'op))
			      (let-values (((temp2 temp3)
					    (find-method/who 'send a0 temp1)))
				(temp2 temp3 '123)))))

	      ;; What about an approximation to a monomorphic inline
	      ;; cache for the Racket object system? This should be
	      ;; much faster than plain old send.
	      (quasi-memoized-normal-send
	       ,(with-method ((a-op (a0 op)))
			     (let ((method (lambda (x) (a-op x))))
			       (measure-ns (if (eq? *memo-class* a0)
					       (*memo-method* 123)
					       (begin
						 (set! *memo-class* a0)
						 (set! *memo-method* method)
						 (method 123)))))))

	      ;; What about an uncached lookup using the trivial
	      ;; vtable format defined above?
	      (unmemoized-simple-lookup
	       ,(measure-ns (unmemo-send b0 op 123)))

	      ;; Finally, the vtable format defined above using an
	      ;; approximation of monomorphic inline caching.
	      (quasi-memoized-simple-lookup
	       ,(measure-ns (memo-send b0 op 123)))

	      )))
