#lang scheme/base
(require
(planet "base.ss" ("schematics" "schemeunit.plt" 3))
(planet schematics/schemeunit:3/test)
(planet williams/science:3/science)
(file "benchmark-log.ss"))
(require (for-syntax
scheme/base
(lib "main-collects.ss" "setup")))
(provide
benchmark-time*
benchmark-time
faster*?
faster?
as-fast*?
as-fast?
check-faster
check-as-fast
check-faster-times
benchmark-case
benchmark-test-case-log-file
this-expression-benchmark-log-file
)
(define (cpu-time thunk)
(let-values (([result cpu real gc] (time-apply thunk null)))
cpu))
(define (benchmark-time* thunk)
(collect-garbage)
(list->vector
(for/list ([i (in-range 10)])
(begin0 (cpu-time thunk)
(collect-garbage)))))
(define-syntax benchmark-time
(syntax-rules ()
[(benchmark-time expr ...)
(benchmark-time* (lambda () expr ...))]))
(define (t-test? times1 times2)
(define p 0.05)
(define n (vector-length times1))
(define std-dev (sqrt (/ (variance times1) (variance times2) 2)))
(define t (/ (- (mean times1) (mean times2)) (* std-dev (sqrt (/ 2 n)))))
(< (t-distribution-pdf t (- (* 2 n) 2)) p))
(define-struct statistics (mean1 mean2 std-dev1 std-dev2 slowdown))
(define (times->statistics times1 times2)
(make-statistics (mean times1)
(mean times2)
(standard-deviation times1)
(standard-deviation times2)
(/ (mean times1) (mean times2))))
(define (compare-times times1 times2 operator printer verbose?)
(let* ([m1 (mean times1)]
[m2 (mean times2)]
[success? (operator m1 m2)])
(when verbose?
(printer (times->statistics times1 times2) success?))
success?))
(define ((current-output-printer name1 name2 comparison) statistics success?)
(newline)
(if success?
(printf "~a was ~a than ~a\n" name1 comparison name2)
(printf "~a was NOT ~a than ~a\n" name1 comparison name2))
(display "------------------------------------------------------------\n")
(printf "Statistics for ~a\n" name1)
(printf "Mean: ~a\n" (statistics-mean1 statistics))
(printf "Std dev.: ~a\n" (statistics-std-dev1 statistics))
(newline)
(printf "Statistics for ~a\n" name2)
(printf "Mean: ~a\n" (statistics-mean2 statistics))
(printf "Std dev.: ~a\n" (statistics-std-dev2 statistics))
(newline)
(printf "~a took ~a the time of ~a\n" name1 (statistics-slowdown statistics) name2)
(display "------------------------------------------------------------\n"))
(define (faster*? name1 times1 name2 times2 [verbose? #t])
(compare-times times1 times2 <
(current-output-printer name1 name2 "faster")
verbose?))
(define-syntax faster?
(syntax-rules ()
[(faster? [name1 expr1 ...] [name2 expr2 ...])
(faster*? name1 (benchmark-time expr1 ...)
name2 (benchmark-time expr2 ...))]))
(define (as-fast*? name1 times1 name2 times2 [verbose? #t])
(compare-times times1 times2 <=
(current-output-printer name1 name2 "as fast")
verbose?))
(define-syntax as-fast?
(syntax-rules ()
[(as-fast? [name1 expr1 ...] [name2 expr2 ...])
(as-fast*? name1 (benchmark-time expr1 ...)
name2 (benchmark-time expr2 ...))]))
(define ((check-printer name1 name2 comparison) statistics success?)
(if success?
#t
(with-check-info
(('name1 name1)
('name2 name2)
('mean1 (statistics-mean1 statistics))
('mean2 (statistics-mean2 statistics))
('std-dev1 (statistics-std-dev1 statistics))
('std-dev2 (statistics-std-dev2 statistics))
('slowdown (statistics-slowdown statistics))
('message (format "~a was not ~a than ~a" name1 comparison name2)))
(fail-check))))
(define-check (check-faster name1 thunk1 name2 thunk2)
(compare-times (benchmark-time* thunk1)
(benchmark-time* thunk2)
<
(check-printer name1 name2 "faster")
#t))
(define-check (check-as-fast name1 thunk1 name2 thunk2)
(compare-times (benchmark-time* thunk1)
(benchmark-time* thunk2)
<=
(check-printer name1 name2 "as fast")
#t))
(define-check (check-faster-times times1 times2)
(compare-times times1
times2
<
(check-printer "1" "2" "faster")
#t))
(define-struct (benchmark-test-case schemeunit-test-case) (log-file))
(define-syntax (this-expression-benchmark-log-file stx)
(syntax-case stx ()
[(this-expression-benchmark-log-file)
(with-syntax
([directory
(let* ([source (syntax-source stx)]
[source (and (path? source) source)]
[local (or (current-load-relative-directory) (current-directory))]
[dir (path->main-collects-relative
(or (and source (file-exists? source)
(let-values ([(base file dir?) (split-path source)])
(and (path? base)
(path->complete-path base local))))
local))])
(if (and (pair? dir) (eq? 'collects (car dir)))
(with-syntax ([d dir])
#'(main-collects-relative->path 'd))
(with-syntax ([d (if (bytes? dir) dir (path->bytes dir))])
#'(bytes->path d))))]
[file-name
(let* ([f (syntax-source stx)]
[f (and f (path? f) (file-exists? f)
(let-values ([(base file dir?) (split-path f)]) file))])
(if f
(with-syntax ([f (path->bytes f)]) #'(bytes->path f))
#'#f))])
(syntax/loc stx
(build-path
directory
(string-append
(path->string file-name)
".benchmark-log"))))]))
(define-syntax (benchmark-case stx)
(syntax-case stx ()
[(_ name expr ...)
(with-syntax ([log-file
(datum->syntax
stx
'(this-expression-benchmark-log-file)
stx)])
(syntax
(make-benchmark-test-case
name
(lambda ()
(let* ([test-thunk (lambda () expr ...)]
[times (benchmark-time* test-thunk)]
[previous-run (find-most-recent-run log-file name)])
(when previous-run
(check-faster-times times (run-times previous-run)))
(add-run log-file name times)
#t))
log-file)))]))