Make snarfing tools more robust to varied C preprocessor behavior.
[bpt/guile.git] / benchmark-suite / benchmark-suite / lib.scm
CommitLineData
02378956 1;;;; benchmark-suite/lib.scm --- generic support for benchmarking
7e822b32 2;;;; Copyright (C) 2002, 2006, 2011, 2012 Free Software Foundation, Inc.
02378956 3;;;;
53befeb7
NJ
4;;;; This program is free software; you can redistribute it and/or
5;;;; modify it under the terms of the GNU Lesser General Public
6;;;; License as published by the Free Software Foundation; either
7;;;; version 3, or (at your option) any later version.
02378956
DH
8;;;;
9;;;; This program is distributed in the hope that it will be useful,
10;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53befeb7 12;;;; GNU Lesser General Public License for more details.
02378956 13;;;;
53befeb7
NJ
14;;;; You should have received a copy of the GNU Lesser General Public
15;;;; License along with this software; see the file COPYING.LESSER.
16;;;; If not, write to the Free Software Foundation, Inc., 51 Franklin
17;;;; Street, Fifth Floor, Boston, MA 02110-1301 USA
02378956
DH
18
19(define-module (benchmark-suite lib)
7e822b32
AW
20 #:use-module (srfi srfi-9)
21 #:export (;; Controlling the execution.
22 iteration-factor
23 scale-iterations
24
25 ;; Running benchmarks.
26 run-benchmark
27 benchmark
28
29 ;; Naming groups of benchmarks in a regular fashion.
30 with-benchmark-prefix with-benchmark-prefix*
31 current-benchmark-prefix format-benchmark-name
32
33 ;; <benchmark-result> accessors
34 benchmark-result:name
35 benchmark-result:iterations
36 benchmark-result:real-time
37 benchmark-result:run-time
38 benchmark-result:gc-time
39 benchmark-result:core-time
40
41 ;; Reporting results in various ways.
42 report current-reporter
43 register-reporter unregister-reporter reporter-registered?
44 make-log-reporter
45 full-reporter
46 user-reporter))
02378956 47
bde9d30b
DH
48
49;;;; If you're using Emacs's Scheme mode:
50;;;; (put 'with-benchmark-prefix 'scheme-indent-function 1)
51;;;; (put 'benchmark 'scheme-indent-function 1)
52
53\f
54;;;; CORE FUNCTIONS
55;;;;
56;;;; The function (run-benchmark name iterations thunk) is the heart of the
57;;;; benchmarking environment. The first parameter NAME is a unique name for
58;;;; the benchmark to be executed (for an explanation of this parameter see
59;;;; below under ;;;; NAMES. The second parameter ITERATIONS is a positive
60;;;; integer value that indicates how often the thunk shall be executed (for
61;;;; an explanation of how iteration counts should be used, see below under
62;;;; ;;;; ITERATION COUNTS). For example:
63;;;;
64;;;; (run-benchmark "small integer addition" 100000 (lambda () (+ 1 1)))
65;;;;
66;;;; This will run the function (lambda () (+ 1 1)) a 100000 times (the
67;;;; iteration count can, however be scaled. See below for details). Some
68;;;; different time data for running the thunk for the given number of
69;;;; iterations is measured and reported.
70;;;;
71;;;; Convenience macro
72;;;;
73;;;; * (benchmark name iterations body) is a short form for
74;;;; (run-benchmark name iterations (lambda () body))
75
76\f
77;;;; NAMES
78;;;;
79;;;; Every benchmark in the benchmark suite has a unique name to be able to
80;;;; compare the results of individual benchmarks across several runs of the
81;;;; benchmark suite.
82;;;;
83;;;; A benchmark name is a list of printable objects. For example:
84;;;; ("ports.scm" "file" "read and write back list of strings")
85;;;; ("ports.scm" "pipe" "read")
86;;;;
87;;;; Benchmark names may contain arbitrary objects, but they always have
88;;;; the following properties:
89;;;; - Benchmark names can be compared with EQUAL?.
90;;;; - Benchmark names can be reliably stored and retrieved with the standard
91;;;; WRITE and READ procedures; doing so preserves their identity.
92;;;;
93;;;; For example:
94;;;;
95;;;; (benchmark "simple addition" 100000 (+ 2 2))
96;;;;
97;;;; In that case, the benchmark name is the list ("simple addition").
98;;;;
99;;;; The WITH-BENCHMARK-PREFIX syntax and WITH-BENCHMARK-PREFIX* procedure
100;;;; establish a prefix for the names of all benchmarks whose results are
101;;;; reported within their dynamic scope. For example:
102;;;;
103;;;; (begin
104;;;; (with-benchmark-prefix "basic arithmetic"
105;;;; (benchmark "addition" 100000 (+ 2 2))
106;;;; (benchmark "subtraction" 100000 (- 4 2)))
107;;;; (benchmark "multiplication" 100000 (* 2 2))))
108;;;;
109;;;; In that example, the three benchmark names are:
110;;;; ("basic arithmetic" "addition"),
111;;;; ("basic arithmetic" "subtraction"), and
112;;;; ("multiplication").
113;;;;
114;;;; WITH-BENCHMARK-PREFIX can be nested. Each WITH-BENCHMARK-PREFIX
679cceed 115;;;; appends a new element to the current prefix:
bde9d30b
DH
116;;;;
117;;;; (with-benchmark-prefix "arithmetic"
118;;;; (with-benchmark-prefix "addition"
119;;;; (benchmark "integer" 100000 (+ 2 2))
120;;;; (benchmark "complex" 100000 (+ 2+3i 4+5i)))
121;;;; (with-benchmark-prefix "subtraction"
122;;;; (benchmark "integer" 100000 (- 2 2))
123;;;; (benchmark "complex" 100000 (- 2+3i 1+2i))))
124;;;;
125;;;; The four benchmark names here are:
126;;;; ("arithmetic" "addition" "integer")
127;;;; ("arithmetic" "addition" "complex")
128;;;; ("arithmetic" "subtraction" "integer")
129;;;; ("arithmetic" "subtraction" "complex")
130;;;;
131;;;; To print a name for a human reader, we DISPLAY its elements,
132;;;; separated by ": ". So, the last set of benchmark names would be
133;;;; reported as:
134;;;;
135;;;; arithmetic: addition: integer
136;;;; arithmetic: addition: complex
137;;;; arithmetic: subtraction: integer
138;;;; arithmetic: subtraction: complex
139;;;;
140;;;; The Guile benchmarks use with-benchmark-prefix to include the name of
141;;;; the source file containing the benchmark in the benchmark name, to
142;;;; provide each file with its own namespace.
143
144\f
145;;;; ITERATION COUNTS
146;;;;
147;;;; Every benchmark has to be given an iteration count that indicates how
148;;;; often it should be executed. The reason is, that in most cases a single
149;;;; execution of the benchmark code would not deliver usable timing results:
150;;;; The resolution of the system time is not arbitrarily fine. Thus, some
151;;;; benchmarks would be executed too quickly to be measured at all. A rule
42ad901d 152;;;; of thumb is, that the longer a benchmark runs, the more exact is the
bde9d30b
DH
153;;;; information about the execution time.
154;;;;
155;;;; However, execution time depends on several influences: First, the
156;;;; machine you are running the benchmark on. Second, the compiler you use.
157;;;; Third, which compiler options you use. Fourth, which version of guile
158;;;; you are using. Fifth, which guile options you are using (for example if
159;;;; you are using the debugging evaluator or not). There are even more
160;;;; influences.
161;;;;
162;;;; For this reason, the same number of iterations for a single benchmark may
163;;;; lead to completely different execution times in different
164;;;; constellations. For someone working on a slow machine, the default
165;;;; execution counts may lead to an inacceptable execution time of the
166;;;; benchmark suite. For someone on a very fast machine, however, it may be
167;;;; desireable to increase the number of iterations in order to increase the
168;;;; accuracy of the time data.
169;;;;
170;;;; For this reason, the benchmark suite allows to scale the number of
171;;;; executions by a global factor, stored in the exported variable
172;;;; iteration-factor. The default for iteration-factor is 1. A number of 2
173;;;; means, that all benchmarks are executed twice as often, which will also
174;;;; roughly double the execution time for the benchmark suite. Similarly, if
175;;;; iteration-factor holds a value of 0.5, only about half the execution time
176;;;; will be required.
177;;;;
178;;;; It is probably a good idea to choose the iteration count for each
179;;;; benchmark such that all benchmarks will take about the same time, for
180;;;; example one second. To achieve this, the benchmark suite holds an empty
181;;;; benchmark in the file 0-reference.bm named "reference benchmark for
182;;;; iteration counts". It's iteration count is calibrated to make the
183;;;; benchmark run about one second on Dirk's laptop :-) If you are adding
184;;;; benchmarks to the suite, it would be nice if you could calibrate the
185;;;; number of iterations such that each of your added benchmarks takes about
186;;;; as long to run as the reference benchmark. But: Don't be too accurate
187;;;; to figure out the correct iteration count.
188
189\f
190;;;; REPORTERS
191;;;;
192;;;; A reporter is a function which we apply to each benchmark outcome.
193;;;; Reporters can log results, print interesting results to the standard
194;;;; output, collect statistics, etc.
195;;;;
196;;;; A reporter function takes the following arguments: NAME ITERATIONS
197;;;; BEFORE AFTER GC-TIME. The argument NAME holds the name of the benchmark,
198;;;; ITERATIONS holds the actual number of iterations that were performed.
199;;;; BEFORE holds the result of the function (times) at the very beginning of
200;;;; the excution of the benchmark, AFTER holds the result of the function
201;;;; (times) after the execution of the benchmark. GC-TIME, finally, holds
202;;;; the difference of calls to (gc-run-time) before and after the execution
203;;;; of the benchmark.
204;;;;
205;;;; This library provides some standard reporters for logging results
206;;;; to a file, reporting interesting results to the user, (FIXME: and
207;;;; collecting totals).
208;;;;
209;;;; You can use the REGISTER-REPORTER function and friends to add whatever
210;;;; reporting functions you like. See under ;;;; TIMING DATA to see how the
211;;;; library helps you to extract relevant timing information from the values
212;;;; ITERATIONS, BEFORE, AFTER and GC-TIME. If you don't register any
213;;;; reporters, the library uses USER-REPORTER, which writes the most
214;;;; interesting results to the standard output.
215
216\f
217;;;; TIME CALCULATION
218;;;;
7e822b32
AW
219;;;; The library uses the guile functions `get-internal-run-time',
220;;;; `get-internal-real-time', and `gc-run-time' to determine the
221;;;; execution time for a single benchmark. Based on these functions,
222;;;; Guile makes a <benchmark-result>, a record containing the elapsed
223;;;; run time, real time, gc time, and possibly other metrics. These
224;;;; times include the time needed to executed the benchmark code
225;;;; itself, but also the surrounding code that implements the loop to
226;;;; run the benchmark code for the given number of times. This is
227;;;; undesirable, since one would prefer to only get the timing data for
228;;;; the benchmarking code.
bde9d30b
DH
229;;;;
230;;;; To cope with this, the benchmarking framework uses a trick: During
7e822b32
AW
231;;;; initialization of the library, the time for executing an empty
232;;;; benchmark is measured and stored. This is an estimate for the time
233;;;; needed by the benchmarking framework itself. For later benchmarks,
234;;;; this time can then be subtracted from the measured execution times.
235;;;; Note that for very short benchmarks, this may result in a negative
236;;;; number.
237;;;;
238;;;; The benchmarking framework provides the following accessors for
239;;;; <benchmark-result> values. Note that all time values are in
240;;;; internal time units; divide by internal-time-units-per-second to
241;;;; get seconds.
242;;;;
243;;;; benchmark-result:name : Return the name of the benchmark.
244;;;;
245;;;; benchmark-result:iterations : Return the number of iterations that
246;;;; this benchmark ran for.
247;;;;
248;;;; benchmark-result:real-time : Return the clock time elapsed while
249;;;; this benchmark executed.
250;;;;
251;;;; benchmark-result:run-time : Return the CPU time elapsed while this
252;;;; benchmark executed, both in user and kernel space.
253;;;;
254;;;; benchmark-result:gc-time : Return the approximate amount of time
255;;;; spent in garbage collection while this benchmark executed, both
256;;;; in user and kernel space.
257;;;;
258;;;; benchmark-result:core-time : Like benchmark-result:run-time, but
259;;;; also estimates the time spent by the framework for the number
260;;;; of iterations, and subtracts off that time from the result.
261;;;;
262
263;;;; This module is used when benchmarking different Guiles, and so it
264;;;; should run on all the Guiles of interest. Currently this set
265;;;; includes Guile 1.8, so be careful with introducing features that
266;;;; only Guile 2.0 supports.
bde9d30b
DH
267
268\f
02378956
DH
269;;;; MISCELLANEOUS
270;;;;
271
7e822b32
AW
272(define-record-type <benchmark-result>
273 (make-benchmark-result name iterations real-time run-time gc-time)
274 benchmark-result?
275 (name benchmark-result:name)
276 (iterations benchmark-result:iterations)
277 (real-time benchmark-result:real-time)
278 (run-time benchmark-result:run-time)
279 (gc-time benchmark-result:gc-time))
280
36ffdf0a 281;;; Perform a division and convert the result to inexact.
7e822b32
AW
282(define (->seconds time)
283 (/ time 1.0 internal-time-units-per-second))
36ffdf0a 284
02378956
DH
285;;; Scale the number of iterations according to the given scaling factor.
286(define iteration-factor 1)
287(define (scale-iterations iterations)
288 (let* ((i (inexact->exact (round (* iterations iteration-factor)))))
289 (if (< i 1) 1 i)))
290
7e822b32
AW
291;;; Parameters.
292(cond-expand
293 (srfi-39 #t)
294 (else (use-modules (srfi srfi-39))))
36ffdf0a 295
02378956
DH
296;;;; CORE FUNCTIONS
297;;;;
298
299;;; The central routine for executing benchmarks.
300;;; The idea is taken from Greg, the GNUstep regression test environment.
7e822b32
AW
301(define benchmark-running? (make-parameter #f))
302(define (run-benchmark name iterations thunk)
303 (if (benchmark-running?)
304 (error "Nested calls to run-benchmark are not permitted."))
305 (if (not (and (integer? iterations) (exact? iterations)))
306 (error "Expected exact integral number of iterations"))
307 (parameterize ((benchmark-running? #t))
308 ;; Warm up the benchmark first. This will resolve any toplevel-ref
309 ;; forms.
310 (thunk)
311 (gc)
312 (let* ((before-gc-time (gc-run-time))
313 (before-real-time (get-internal-real-time))
314 (before-run-time (get-internal-run-time)))
315 (do ((i iterations (1- i)))
316 ((zero? i))
317 (thunk))
318 (let ((after-run-time (get-internal-run-time))
319 (after-real-time (get-internal-real-time))
320 (after-gc-time (gc-run-time)))
321 (report (make-benchmark-result (full-name name) iterations
322 (- after-real-time before-real-time)
323 (- after-run-time before-run-time)
324 (- after-gc-time before-gc-time)))))))
02378956
DH
325
326;;; A short form for benchmarks.
7e822b32
AW
327(cond-expand
328 (guile-2
329 (define-syntax-rule (benchmark name iterations body body* ...)
330 (run-benchmark name iterations (lambda () body body* ...))))
331 (else
332 (defmacro benchmark (name iterations body . rest)
333 `(run-benchmark ,name ,iterations (lambda () ,body ,@rest)))))
02378956
DH
334
335\f
336;;;; BENCHMARK NAMES
337;;;;
338
339;;;; Turn a benchmark name into a nice human-readable string.
340(define (format-benchmark-name name)
7e822b32 341 (string-join name ": "))
02378956
DH
342
343;;;; For a given benchmark-name, deliver the full name including all prefixes.
344(define (full-name name)
345 (append (current-benchmark-prefix) (list name)))
346
7e822b32
AW
347;;; A parameter containing the current benchmark prefix, as a list.
348(define current-benchmark-prefix
349 (make-parameter '()))
02378956
DH
350
351;;; Postpend PREFIX to the current name prefix while evaluting THUNK.
352;;; The name prefix is only changed within the dynamic scope of the
353;;; call to with-benchmark-prefix*. Return the value returned by THUNK.
354(define (with-benchmark-prefix* prefix thunk)
7e822b32 355 (parameterize ((current-benchmark-prefix (full-name prefix)))
02378956
DH
356 (thunk)))
357
358;;; (with-benchmark-prefix PREFIX BODY ...)
359;;; Postpend PREFIX to the current name prefix while evaluating BODY ...
360;;; The name prefix is only changed within the dynamic scope of the
361;;; with-benchmark-prefix expression. Return the value returned by the last
362;;; BODY expression.
7e822b32
AW
363(cond-expand
364 (guile-2
365 (define-syntax-rule (with-benchmark-prefix prefix body body* ...)
366 (with-benchmark-prefix* prefix (lambda () body body* ...))))
367 (else
368 (defmacro with-benchmark-prefix (prefix . body)
369 `(with-benchmark-prefix* ,prefix (lambda () ,@body)))))
02378956
DH
370
371\f
7e822b32 372;;;; Benchmark results
02378956
DH
373;;;;
374
7e822b32 375(define *calibration-result*
02378956
DH
376 "<will be set during initialization>")
377
7e822b32
AW
378(define (benchmark-overhead iterations accessor)
379 (* (/ iterations 1.0 (benchmark-result:iterations *calibration-result*))
380 (accessor *calibration-result*)))
02378956 381
7e822b32
AW
382(define (benchmark-result:core-time result)
383 (- (benchmark-result:run-time result)
384 (benchmark-overhead (benchmark-result:iterations result)
385 benchmark-result:run-time)))
02378956
DH
386
387\f
388;;;; REPORTERS
389;;;;
390
7e822b32
AW
391;;; The global set of reporters.
392(define report-hook (make-hook 1))
393
394(define (default-reporter result)
395 (if (hook-empty? report-hook)
396 (user-reporter result)
397 (run-hook report-hook result)))
02378956 398
7e822b32
AW
399(define current-reporter
400 (make-parameter default-reporter))
02378956 401
02378956 402(define (register-reporter reporter)
7e822b32 403 (add-hook! report-hook reporter))
02378956 404
02378956 405(define (unregister-reporter reporter)
7e822b32 406 (remove-hook! report-hook reporter))
02378956
DH
407
408;;; Return true iff REPORTER is in the current set of reporter functions.
409(define (reporter-registered? reporter)
7e822b32 410 (if (memq reporter (hook->list report-hook)) #t #f))
02378956
DH
411
412;;; Send RESULT to all currently registered reporter functions.
7e822b32
AW
413(define (report result)
414 ((current-reporter) result))
02378956
DH
415
416\f
417;;;; Some useful standard reporters:
8f28ea31 418;;;; Log reporters write all benchmark results to a given log file.
02378956
DH
419;;;; Full reporters write all benchmark results to the standard output.
420;;;; User reporters write some interesting results to the standard output.
421
422;;; Display a single benchmark result to the given port
7e822b32
AW
423(define (print-result port result)
424 (let ((name (format-benchmark-name (benchmark-result:name result)))
425 (iterations (benchmark-result:iterations result))
426 (real-time (benchmark-result:real-time result))
427 (run-time (benchmark-result:run-time result))
428 (gc-time (benchmark-result:gc-time result))
429 (core-time (benchmark-result:core-time result)))
02378956 430 (write (list name iterations
7e822b32
AW
431 'total (->seconds real-time)
432 'user (->seconds run-time)
433 'system 0
434 'frame (->seconds (- run-time core-time))
435 'benchmark (->seconds core-time)
436 'user/interp (->seconds (- run-time gc-time))
437 'bench/interp (->seconds (- core-time gc-time))
438 'gc (->seconds gc-time))
02378956
DH
439 port)
440 (newline port)))
441
442;;; Return a reporter procedure which prints all results to the file
443;;; FILE, in human-readable form. FILE may be a filename, or a port.
444(define (make-log-reporter file)
445 (let ((port (if (output-port? file) file
446 (open-output-file file))))
7e822b32
AW
447 (lambda (result)
448 (print-result port result)
02378956
DH
449 (force-output port))))
450
451;;; A reporter that reports all results to the user.
7e822b32
AW
452(define (full-reporter result)
453 (print-result (current-output-port) result))
02378956
DH
454
455;;; Display interesting results of a single benchmark to the given port
7e822b32
AW
456(define (print-user-result port result)
457 (let ((name (format-benchmark-name (benchmark-result:name result)))
458 (iterations (benchmark-result:iterations result))
459 (real-time (benchmark-result:real-time result))
460 (run-time (benchmark-result:run-time result))
461 (gc-time (benchmark-result:gc-time result))
462 (core-time (benchmark-result:core-time result)))
463 (write (list name iterations
464 'real (->seconds real-time)
465 'real/iteration (->seconds (/ real-time iterations))
466 'run/iteration (->seconds (/ run-time iterations))
467 'core/iteration (->seconds (/ core-time iterations))
468 'gc (->seconds gc-time))
02378956
DH
469 port)
470 (newline port)))
471
472;;; A reporter that reports interesting results to the user.
7e822b32
AW
473(define (user-reporter result)
474 (print-user-result (current-output-port) result))
02378956
DH
475
476\f
477;;;; Initialize the benchmarking system:
478;;;;
479
7e822b32
AW
480(define (calibrate-benchmark-framework)
481 (display ";; running guile version ")
482 (display (version))
483 (newline)
484 (display ";; calibrating the benchmarking framework...")
485 (newline)
486 (parameterize ((current-reporter
487 (lambda (result)
488 (set! *calibration-result* result)
489 (display ";; calibration: ")
490 (print-user-result (current-output-port) result))))
491 (benchmark "empty initialization benchmark" 10000000 #t)))
492
493(calibrate-benchmark-framework)