3 # benchmark.rb - a performance benchmarking library
7 # Created by Gotoken (gotoken@notwork.org).
9 # Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and
10 # Gavin Sinclair (editing).
16 # The Benchmark module provides methods for benchmarking Ruby code, giving
17 # detailed reports on the time taken for each task.
20 # The Benchmark module provides methods to measure and report the time
21 # used to execute Ruby code.
23 # * Measure the time to construct the string given by the expression
24 # <tt>"a"*1_000_000</tt>:
28 # puts Benchmark.measure { "a"*1_000_000 }
30 # On my machine (FreeBSD 3.2 on P5, 100MHz) this generates:
32 # 1.166667 0.050000 1.216667 ( 0.571355)
34 # This report shows the user CPU time, system CPU time, the sum of
35 # the user and system CPU times, and the elapsed real time. The unit
38 # * Do some experiments sequentially using the #bm method:
44 # x.report { for i in 1..n; a = "1"; end }
45 # x.report { n.times do ; a = "1"; end }
46 # x.report { 1.upto(n) do ; a = "1"; end }
51 # user system total real
52 # 1.033333 0.016667 1.016667 ( 0.492106)
53 # 1.483333 0.000000 1.483333 ( 0.694605)
54 # 1.516667 0.000000 1.516667 ( 0.711077)
56 # * Continuing the previous example, put a label in each report:
61 # Benchmark.bm(7) do |x|
62 # x.report("for:") { for i in 1..n; a = "1"; end }
63 # x.report("times:") { n.times do ; a = "1"; end }
64 # x.report("upto:") { 1.upto(n) do ; a = "1"; end }
69 # user system total real
70 # for: 1.050000 0.000000 1.050000 ( 0.503462)
71 # times: 1.533333 0.016667 1.550000 ( 0.735473)
72 # upto: 1.500000 0.016667 1.516667 ( 0.711239)
75 # * The times for some benchmarks depend on the order in which items
76 # are run. These differences are due to the cost of memory
77 # allocation and garbage collection. To avoid these discrepancies,
78 # the #bmbm method is provided. For example, to compare ways to
79 # sort an array of floats:
83 # array = (1..1000000).map { rand }
85 # Benchmark.bmbm do |x|
86 # x.report("sort!") { array.dup.sort! }
87 # x.report("sort") { array.dup.sort }
92 # Rehearsal -----------------------------------------
93 # sort! 11.928000 0.010000 11.938000 ( 12.756000)
94 # sort 13.048000 0.020000 13.068000 ( 13.857000)
95 # ------------------------------- total: 25.006000sec
97 # user system total real
98 # sort! 12.959000 0.010000 12.969000 ( 13.793000)
99 # sort 12.007000 0.000000 12.007000 ( 12.791000)
102 # * Report statistics of sequential experiments with unique labels,
103 # using the #benchmark method:
105 # require 'benchmark'
108 # Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR, ">total:", ">avg:") do |x|
109 # tf = x.report("for:") { for i in 1..n; a = "1"; end }
110 # tt = x.report("times:") { n.times do ; a = "1"; end }
111 # tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
112 # [tf+tt+tu, (tf+tt+tu)/3]
117 # user system total real
118 # for: 1.016667 0.016667 1.033333 ( 0.485749)
119 # times: 1.450000 0.016667 1.466667 ( 0.681367)
120 # upto: 1.533333 0.000000 1.533333 ( 0.722166)
121 # >total: 4.000000 0.033333 4.033333 ( 1.889282)
122 # >avg: 1.333333 0.011111 1.344444 ( 0.629761)
126 BENCHMARK_VERSION = "2002-04-25" #:nodoc"
128 def Benchmark::times() # :nodoc:
133 # Invokes the block with a <tt>Benchmark::Report</tt> object, which
134 # may be used to collect and report on the results of individual
135 # benchmark tests. Reserves <i>label_width</i> leading spaces for
136 # labels on each line. Prints _caption_ at the top of the
137 # report, and uses _fmt_ to format each line.
138 # If the block returns an array of
139 # <tt>Benchmark::Tms</tt> objects, these will be used to format
140 # additional lines of output. If _label_ parameters are
141 # given, these are used to label these extra lines.
143 # _Note_: Other methods provide a simpler interface to this one, and are
144 # suitable for nearly all benchmarking requirements. See the examples in
145 # Benchmark, and the #bm and #bmbm methods.
149 # require 'benchmark'
150 # include Benchmark # we need the CAPTION and FMTSTR constants
153 # Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR, ">total:", ">avg:") do |x|
154 # tf = x.report("for:") { for i in 1..n; a = "1"; end }
155 # tt = x.report("times:") { n.times do ; a = "1"; end }
156 # tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
157 # [tf+tt+tu, (tf+tt+tu)/3]
162 # user system total real
163 # for: 1.016667 0.016667 1.033333 ( 0.485749)
164 # times: 1.450000 0.016667 1.466667 ( 0.681367)
165 # upto: 1.533333 0.000000 1.533333 ( 0.722166)
166 # >total: 4.000000 0.033333 4.033333 ( 1.889282)
167 # >avg: 1.333333 0.011111 1.344444 ( 0.629761)
170 def benchmark(caption = "", label_width = nil, fmtstr = nil, *labels) # :yield: report
175 raise ArgumentError, "no block" unless iterator?
177 results = yield(Report.new(label_width, fmtstr))
178 Array === results and results.grep(Tms).each {|t|
179 print((labels.shift || t.label || "").ljust(label_width),
186 # A simple interface to the #benchmark method, #bm is generates sequential reports
187 # with labels. The parameters have the same meaning as for #benchmark.
189 # require 'benchmark'
192 # Benchmark.bm(7) do |x|
193 # x.report("for:") { for i in 1..n; a = "1"; end }
194 # x.report("times:") { n.times do ; a = "1"; end }
195 # x.report("upto:") { 1.upto(n) do ; a = "1"; end }
200 # user system total real
201 # for: 1.050000 0.000000 1.050000 ( 0.503462)
202 # times: 1.533333 0.016667 1.550000 ( 0.735473)
203 # upto: 1.500000 0.016667 1.516667 ( 0.711239)
206 def bm(label_width = 0, *labels, &blk) # :yield: report
207 benchmark(" "*label_width + CAPTION, label_width, FMTSTR, *labels, &blk)
211 # Sometimes benchmark results are skewed because code executed
212 # earlier encounters different garbage collection overheads than
213 # that run later. #bmbm attempts to minimize this effect by running
214 # the tests twice, the first time as a rehearsal in order to get the
215 # runtime environment stable, the second time for
216 # real. <tt>GC.start</tt> is executed before the start of each of
217 # the real timings; the cost of this is not included in the
218 # timings. In reality, though, there's only so much that #bmbm can
219 # do, and the results are not guaranteed to be isolated from garbage
220 # collection and other effects.
222 # Because #bmbm takes two passes through the tests, it can
223 # calculate the required label width.
225 # require 'benchmark'
227 # array = (1..1000000).map { rand }
229 # Benchmark.bmbm do |x|
230 # x.report("sort!") { array.dup.sort! }
231 # x.report("sort") { array.dup.sort }
236 # Rehearsal -----------------------------------------
237 # sort! 11.928000 0.010000 11.938000 ( 12.756000)
238 # sort 13.048000 0.020000 13.068000 ( 13.857000)
239 # ------------------------------- total: 25.006000sec
241 # user system total real
242 # sort! 12.959000 0.010000 12.969000 ( 13.793000)
243 # sort 12.007000 0.000000 12.007000 ( 12.791000)
245 # #bmbm yields a Benchmark::Job object and returns an array of
246 # Benchmark::Tms objects.
248 def bmbm(width = 0, &blk) # :yield: job
257 puts '-'*(width+CAPTION.length - "Rehearsal ".length)
259 job.list.each{|label,item|
260 print(label.ljust(width))
261 res = Benchmark::measure(&item)
265 sum = Tms.new; list.each{|i| sum += i}
266 ets = sum.format("total: %tsec")
268 "-"*(width+CAPTION.length-ets.length-1), ets)
271 print ' '*width, CAPTION
274 job.list.each{|label,item|
276 print label.ljust(width)
277 res = Benchmark::measure(&item)
280 list.push [label, res]
288 # Returns the time used to execute the given block as a
289 # Benchmark::Tms object.
291 def measure(label = "") # :yield:
292 t0, r0 = Benchmark.times, Time.now
294 t1, r1 = Benchmark.times, Time.now
295 Benchmark::Tms.new(t1.utime - t0.utime,
297 t1.cutime - t0.cutime,
298 t1.cstime - t0.cstime,
304 # Returns the elapsed real time used to execute the given block.
306 def realtime(&blk) # :yield:
316 # A Job is a sequence of labelled blocks to be processed by the
317 # Benchmark.bmbm method. It is of little direct interest to the user.
321 # Returns an initialized Job instance.
322 # Usually, one doesn't call this method directly, as new
323 # Job objects are created by the #bmbm method.
324 # _width_ is a initial value for the label offset used in formatting;
325 # the #bmbm method passes its _width_ argument to this constructor.
327 def initialize(width)
333 # Registers the given label and block pair in the job list.
335 def item(label = "", &blk) # :yield:
336 raise ArgumentError, "no block" unless block_given?
339 @width = w if @width < w
340 @list.push [label, blk]
346 # An array of 2-element arrays, consisting of label and block pairs.
349 # Length of the widest label in the #list, plus one.
353 module_function :benchmark, :measure, :realtime, :bm, :bmbm
358 # This class is used by the Benchmark.benchmark and Benchmark.bm methods.
359 # It is of little direct interest to the user.
361 class Report # :nodoc:
363 # Returns an initialized Report instance.
364 # Usually, one doesn't call this method directly, as new
365 # Report objects are created by the #benchmark and #bm methods.
366 # _width_ and _fmtstr_ are the label offset and
367 # format string used by Tms#format.
369 def initialize(width = 0, fmtstr = nil)
370 @width, @fmtstr = width, fmtstr
374 # Prints the _label_ and measured time for the block,
375 # formatted by _fmt_. See Tms#format for the
378 def item(label = "", *fmt, &blk) # :yield:
379 print label.ljust(@width)
380 res = Benchmark::measure(&blk)
381 print res.format(@fmtstr, *fmt)
391 # A data object, representing the times associated with a benchmark
395 CAPTION = " user system total real\n"
396 FMTSTR = "%10.6u %10.6y %10.6t %10.6r\n"
404 # User CPU time of children
407 # System CPU time of children
413 # Total time, that is _utime_ + _stime_ + _cutime_ + _cstime_
420 # Returns an initialized Tms object which has
421 # _u_ as the user CPU time, _s_ as the system CPU time,
422 # _cu_ as the children's user CPU time, _cs_ as the children's
423 # system CPU time, _real_ as the elapsed real time and _l_
426 def initialize(u = 0.0, s = 0.0, cu = 0.0, cs = 0.0, real = 0.0, l = nil)
427 @utime, @stime, @cutime, @cstime, @real, @label = u, s, cu, cs, real, l
428 @total = @utime + @stime + @cutime + @cstime
432 # Returns a new Tms object whose times are the sum of the times for this
433 # Tms object, plus the time required to execute the code block (_blk_).
435 def add(&blk) # :yield:
436 self + Benchmark::measure(&blk)
440 # An in-place version of #add.
443 t = Benchmark::measure(&blk)
444 @utime = utime + t.utime
445 @stime = stime + t.stime
446 @cutime = cutime + t.cutime
447 @cstime = cstime + t.cstime
448 @real = real + t.real
453 # Returns a new Tms object obtained by memberwise summation
454 # of the individual times for this Tms object with those of the other
456 # This method and #/() are useful for taking statistics.
458 def +(other); memberwise(:+, other) end
461 # Returns a new Tms object obtained by memberwise subtraction
462 # of the individual times for the other Tms object from those of this
465 def -(other); memberwise(:-, other) end
468 # Returns a new Tms object obtained by memberwise multiplication
469 # of the individual times for this Tms object by _x_.
471 def *(x); memberwise(:*, x) end
474 # Returns a new Tms object obtained by memberwise division
475 # of the individual times for this Tms object by _x_.
476 # This method and #+() are useful for taking statistics.
478 def /(x); memberwise(:/, x) end
481 # Returns the contents of this Tms object as
482 # a formatted string, according to a format string
483 # like that passed to Kernel.format. In addition, #format
484 # accepts the following extensions:
486 # <tt>%u</tt>:: Replaced by the user CPU time, as reported by Tms#utime.
487 # <tt>%y</tt>:: Replaced by the system CPU time, as reported by #stime (Mnemonic: y of "s*y*stem")
488 # <tt>%U</tt>:: Replaced by the children's user CPU time, as reported by Tms#cutime
489 # <tt>%Y</tt>:: Replaced by the children's system CPU time, as reported by Tms#cstime
490 # <tt>%t</tt>:: Replaced by the total CPU time, as reported by Tms#total
491 # <tt>%r</tt>:: Replaced by the elapsed real time, as reported by Tms#real
492 # <tt>%n</tt>:: Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame")
494 # If _fmtstr_ is not given, FMTSTR is used as default value, detailing the
495 # user, system and real elapsed time.
497 def format(arg0 = nil, *args)
498 fmtstr = (arg0 || FMTSTR).dup
499 fmtstr.gsub!(/(%[-+\.\d]*)n/){"#{$1}s" % label}
500 fmtstr.gsub!(/(%[-+\.\d]*)u/){"#{$1}f" % utime}
501 fmtstr.gsub!(/(%[-+\.\d]*)y/){"#{$1}f" % stime}
502 fmtstr.gsub!(/(%[-+\.\d]*)U/){"#{$1}f" % cutime}
503 fmtstr.gsub!(/(%[-+\.\d]*)Y/){"#{$1}f" % cstime}
504 fmtstr.gsub!(/(%[-+\.\d]*)t/){"#{$1}f" % total}
505 fmtstr.gsub!(/(%[-+\.\d]*)r/){"(#{$1}f)" % real}
506 arg0 ? Kernel::format(fmtstr, *args) : fmtstr
517 # Returns a new 6-element array, consisting of the
518 # label, user CPU time, system CPU time, children's
519 # user CPU time, children's system CPU time and elapsed
523 [@label, @utime, @stime, @cutime, @cstime, @real]
527 def memberwise(op, x)
530 Benchmark::Tms.new(utime.__send__(op, x.utime),
531 stime.__send__(op, x.stime),
532 cutime.__send__(op, x.cutime),
533 cstime.__send__(op, x.cstime),
534 real.__send__(op, x.real)
537 Benchmark::Tms.new(utime.__send__(op, x),
538 stime.__send__(op, x),
539 cutime.__send__(op, x),
540 cstime.__send__(op, x),
547 # The default caption string (heading above the output times).
548 CAPTION = Benchmark::Tms::CAPTION
550 # The default format string used to display times. See also Benchmark::Tms#format.
551 FMTSTR = Benchmark::Tms::FMTSTR
557 n = ARGV[0].to_i.nonzero? || 50000
558 puts %Q([#{n} times iterations of `a = "1"'])
559 benchmark(" " + CAPTION, 7, FMTSTR) do |x|
560 x.report("for:") {for i in 1..n; a = "1"; end} # Benchmark::measure
561 x.report("times:") {n.times do ; a = "1"; end}
562 x.report("upto:") {1.upto(n) do ; a = "1"; end}
567 measure{for i in 1..n; a = "1"; end}, # Benchmark::measure
568 measure{n.times do ; a = "1"; end},
569 measure{1.upto(n) do ; a = "1"; end}