PageRenderTime 61ms CodeModel.GetById 19ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 1ms

/IronPython_Main/External.LCA_RESTRICTED/Languages/Ruby/redist-libs/ruby/1.9.1/benchmark.rb

#
Ruby | 573 lines | 189 code | 57 blank | 327 comment | 7 complexity | 118d5fd3780d6a9bcf68e68e7a13c5c0 MD5 | raw file
  1=begin
  2#
  3# benchmark.rb - a performance benchmarking library
  4#
  5# $Id: benchmark.rb 27197 2010-04-02 18:22:29Z kazu $
  6#
  7# Created by Gotoken (gotoken@notwork.org).
  8#
  9# Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and
 10# Gavin Sinclair (editing).
 11#
 12=end
 13
 14# == Overview
 15#
 16# The Benchmark module provides methods for benchmarking Ruby code, giving
 17# detailed reports on the time taken for each task.
 18#
 19
 20# The Benchmark module provides methods to measure and report the time
 21# used to execute Ruby code.
 22#
 23# * Measure the time to construct the string given by the expression
 24#   <tt>"a"*1_000_000</tt>:
 25#
 26#       require 'benchmark'
 27#
 28#       puts Benchmark.measure { "a"*1_000_000 }
 29#
 30#   On my machine (FreeBSD 3.2 on P5, 100MHz) this generates:
 31#
 32#       1.166667   0.050000   1.216667 (  0.571355)
 33#
 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
 36#   of time is seconds.
 37#
 38# * Do some experiments sequentially using the #bm method:
 39#
 40#       require 'benchmark'
 41#
 42#       n = 50000
 43#       Benchmark.bm do |x|
 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 }
 47#       end
 48#
 49#   The result:
 50#
 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)
 55#
 56# * Continuing the previous example, put a label in each report:
 57#
 58#       require 'benchmark'
 59#
 60#       n = 50000
 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 }
 65#       end
 66#
 67# The result:
 68#
 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)
 73#
 74#
 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:
 80#
 81#       require 'benchmark'
 82#
 83#       array = (1..1000000).map { rand }
 84#
 85#       Benchmark.bmbm do |x|
 86#         x.report("sort!") { array.dup.sort! }
 87#         x.report("sort")  { array.dup.sort  }
 88#       end
 89#
 90#   The result:
 91#
 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
 96#
 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)
100#
101#
102# * Report statistics of sequential experiments with unique labels,
103#   using the #benchmark method:
104#
105#       require 'benchmark'
106#       include Benchmark         # we need the CAPTION and FMTSTR constants
107#
108#       n = 50000
109#       Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR, ">total:", ">avg:") do |x|
110#         tf = x.report("for:")   { for i in 1..n; a = "1"; end }
111#         tt = x.report("times:") { n.times do   ; a = "1"; end }
112#         tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
113#         [tf+tt+tu, (tf+tt+tu)/3]
114#       end
115#
116#   The result:
117#
118#                     user     system      total        real
119#        for:     1.016667   0.016667   1.033333 (  0.485749)
120#        times:   1.450000   0.016667   1.466667 (  0.681367)
121#        upto:    1.533333   0.000000   1.533333 (  0.722166)
122#        >total:  4.000000   0.033333   4.033333 (  1.889282)
123#        >avg:    1.333333   0.011111   1.344444 (  0.629761)
124
125module Benchmark
126
127  BENCHMARK_VERSION = "2002-04-25" #:nodoc"
128
129  def Benchmark::times() # :nodoc:
130      Process::times()
131  end
132
133
134  # Invokes the block with a <tt>Benchmark::Report</tt> object, which
135  # may be used to collect and report on the results of individual
136  # benchmark tests. Reserves <i>label_width</i> leading spaces for
137  # labels on each line. Prints _caption_ at the top of the
138  # report, and uses _fmt_ to format each line.
139  # If the block returns an array of
140  # <tt>Benchmark::Tms</tt> objects, these will be used to format
141  # additional lines of output. If _label_ parameters are
142  # given, these are used to label these extra lines.
143  #
144  # _Note_: Other methods provide a simpler interface to this one, and are
145  # suitable for nearly all benchmarking requirements.  See the examples in
146  # Benchmark, and the #bm and #bmbm methods.
147  #
148  # Example:
149  #
150  #     require 'benchmark'
151  #     include Benchmark          # we need the CAPTION and FMTSTR constants
152  #
153  #     n = 50000
154  #     Benchmark.benchmark(" "*7 + CAPTION, 7, FMTSTR, ">total:", ">avg:") do |x|
155  #       tf = x.report("for:")   { for i in 1..n; a = "1"; end }
156  #       tt = x.report("times:") { n.times do   ; a = "1"; end }
157  #       tu = x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
158  #       [tf+tt+tu, (tf+tt+tu)/3]
159  #     end
160  #
161  # <i>Generates:</i>
162  #
163  #                     user     system      total        real
164  #        for:     1.016667   0.016667   1.033333 (  0.485749)
165  #        times:   1.450000   0.016667   1.466667 (  0.681367)
166  #        upto:    1.533333   0.000000   1.533333 (  0.722166)
167  #        >total:  4.000000   0.033333   4.033333 (  1.889282)
168  #        >avg:    1.333333   0.011111   1.344444 (  0.629761)
169  #
170
171  def benchmark(caption = "", label_width = nil, fmtstr = nil, *labels) # :yield: report
172    sync = STDOUT.sync
173    STDOUT.sync = true
174    label_width ||= 0
175    fmtstr ||= FMTSTR
176    raise ArgumentError, "no block" unless iterator?
177    print caption
178    results = yield(Report.new(label_width, fmtstr))
179    Array === results and results.grep(Tms).each {|t|
180      print((labels.shift || t.label || "").ljust(label_width),
181            t.format(fmtstr))
182    }
183    STDOUT.sync = sync
184  end
185
186
187  # A simple interface to the #benchmark method, #bm is generates sequential reports
188  # with labels.  The parameters have the same meaning as for #benchmark.
189  #
190  #     require 'benchmark'
191  #
192  #     n = 50000
193  #     Benchmark.bm(7) do |x|
194  #       x.report("for:")   { for i in 1..n; a = "1"; end }
195  #       x.report("times:") { n.times do   ; a = "1"; end }
196  #       x.report("upto:")  { 1.upto(n) do ; a = "1"; end }
197  #     end
198  #
199  # <i>Generates:</i>
200  #
201  #                     user     system      total        real
202  #        for:     1.050000   0.000000   1.050000 (  0.503462)
203  #        times:   1.533333   0.016667   1.550000 (  0.735473)
204  #        upto:    1.500000   0.016667   1.516667 (  0.711239)
205  #
206
207  def bm(label_width = 0, *labels, &blk) # :yield: report
208    benchmark(" "*label_width + CAPTION, label_width, FMTSTR, *labels, &blk)
209  end
210
211
212  # Sometimes benchmark results are skewed because code executed
213  # earlier encounters different garbage collection overheads than
214  # that run later. #bmbm attempts to minimize this effect by running
215  # the tests twice, the first time as a rehearsal in order to get the
216  # runtime environment stable, the second time for
217  # real. <tt>GC.start</tt> is executed before the start of each of
218  # the real timings; the cost of this is not included in the
219  # timings. In reality, though, there's only so much that #bmbm can
220  # do, and the results are not guaranteed to be isolated from garbage
221  # collection and other effects.
222  #
223  # Because #bmbm takes two passes through the tests, it can
224  # calculate the required label width.
225  #
226  #       require 'benchmark'
227  #
228  #       array = (1..1000000).map { rand }
229  #
230  #       Benchmark.bmbm do |x|
231  #         x.report("sort!") { array.dup.sort! }
232  #         x.report("sort")  { array.dup.sort  }
233  #       end
234  #
235  # <i>Generates:</i>
236  #
237  #        Rehearsal -----------------------------------------
238  #        sort!  11.928000   0.010000  11.938000 ( 12.756000)
239  #        sort   13.048000   0.020000  13.068000 ( 13.857000)
240  #        ------------------------------- total: 25.006000sec
241  #
242  #                    user     system      total        real
243  #        sort!  12.959000   0.010000  12.969000 ( 13.793000)
244  #        sort   12.007000   0.000000  12.007000 ( 12.791000)
245  #
246  # #bmbm yields a Benchmark::Job object and returns an array of
247  # Benchmark::Tms objects.
248  #
249  def bmbm(width = 0, &blk) # :yield: job
250    job = Job.new(width)
251    yield(job)
252    width = job.width
253    sync = STDOUT.sync
254    STDOUT.sync = true
255
256    # rehearsal
257    print "Rehearsal "
258    puts '-'*(width+CAPTION.length - "Rehearsal ".length)
259    list = []
260    job.list.each{|label,item|
261      print(label.ljust(width))
262      res = Benchmark::measure(&item)
263      print res.format()
264      list.push res
265    }
266    sum = Tms.new; list.each{|i| sum += i}
267    ets = sum.format("total: %tsec")
268    printf("%s %s\n\n",
269           "-"*(width+CAPTION.length-ets.length-1), ets)
270
271    # take
272    print ' '*width, CAPTION
273    list = []
274    ary = []
275    job.list.each{|label,item|
276      GC::start
277      print label.ljust(width)
278      res = Benchmark::measure(&item)
279      print res.format()
280      ary.push res
281      list.push [label, res]
282    }
283
284    STDOUT.sync = sync
285    ary
286  end
287
288  #
289  # Returns the time used to execute the given block as a
290  # Benchmark::Tms object.
291  #
292  def measure(label = "") # :yield:
293    t0, r0 = Benchmark.times, Time.now
294    yield
295    t1, r1 = Benchmark.times, Time.now
296    Benchmark::Tms.new(t1.utime  - t0.utime,
297                       t1.stime  - t0.stime,
298                       t1.cutime - t0.cutime,
299                       t1.cstime - t0.cstime,
300                       r1.to_f - r0.to_f,
301                       label)
302  end
303
304  #
305  # Returns the elapsed real time used to execute the given block.
306  #
307  def realtime(&blk) # :yield:
308    r0 = Time.now
309    yield
310    r1 = Time.now
311    r1.to_f - r0.to_f
312  end
313
314
315
316  #
317  # A Job is a sequence of labelled blocks to be processed by the
318  # Benchmark.bmbm method.  It is of little direct interest to the user.
319  #
320  class Job # :nodoc:
321    #
322    # Returns an initialized Job instance.
323    # Usually, one doesn't call this method directly, as new
324    # Job objects are created by the #bmbm method.
325    # _width_ is a initial value for the label offset used in formatting;
326    # the #bmbm method passes its _width_ argument to this constructor.
327    #
328    def initialize(width)
329      @width = width
330      @list = []
331    end
332
333    #
334    # Registers the given label and block pair in the job list.
335    #
336    def item(label = "", &blk) # :yield:
337      raise ArgumentError, "no block" unless block_given?
338      label += ' '
339      w = label.length
340      @width = w if @width < w
341      @list.push [label, blk]
342      self
343    end
344
345    alias report item
346
347    # An array of 2-element arrays, consisting of label and block pairs.
348    attr_reader :list
349
350    # Length of the widest label in the #list, plus one.
351    attr_reader :width
352  end
353
354  module_function :benchmark, :measure, :realtime, :bm, :bmbm
355
356
357
358  #
359  # This class is used by the Benchmark.benchmark and Benchmark.bm methods.
360  # It is of little direct interest to the user.
361  #
362  class Report # :nodoc:
363    #
364    # Returns an initialized Report instance.
365    # Usually, one doesn't call this method directly, as new
366    # Report objects are created by the #benchmark and #bm methods.
367    # _width_ and _fmtstr_ are the label offset and
368    # format string used by Tms#format.
369    #
370    def initialize(width = 0, fmtstr = nil)
371      @width, @fmtstr = width, fmtstr
372    end
373
374    #
375    # Prints the _label_ and measured time for the block,
376    # formatted by _fmt_. See Tms#format for the
377    # formatting rules.
378    #
379    def item(label = "", *fmt, &blk) # :yield:
380      print label.ljust(@width)
381      res = Benchmark::measure(&blk)
382      print res.format(@fmtstr, *fmt)
383      res
384    end
385
386    alias report item
387  end
388
389
390
391  #
392  # A data object, representing the times associated with a benchmark
393  # measurement.
394  #
395  class Tms
396    CAPTION = "      user     system      total        real\n"
397    FMTSTR = "%10.6u %10.6y %10.6t %10.6r\n"
398
399    # User CPU time
400    attr_reader :utime
401
402    # System CPU time
403    attr_reader :stime
404
405    # User CPU time of children
406    attr_reader :cutime
407
408    # System CPU time of children
409    attr_reader :cstime
410
411    # Elapsed real time
412    attr_reader :real
413
414    # Total time, that is _utime_ + _stime_ + _cutime_ + _cstime_
415    attr_reader :total
416
417    # Label
418    attr_reader :label
419
420    #
421    # Returns an initialized Tms object which has
422    # _u_ as the user CPU time, _s_ as the system CPU time,
423    # _cu_ as the children's user CPU time, _cs_ as the children's
424    # system CPU time, _real_ as the elapsed real time and _l_
425    # as the label.
426    #
427    def initialize(u = 0.0, s = 0.0, cu = 0.0, cs = 0.0, real = 0.0, l = nil)
428      @utime, @stime, @cutime, @cstime, @real, @label = u, s, cu, cs, real, l
429      @total = @utime + @stime + @cutime + @cstime
430    end
431
432    #
433    # Returns a new Tms object whose times are the sum of the times for this
434    # Tms object, plus the time required to execute the code block (_blk_).
435    #
436    def add(&blk) # :yield:
437      self + Benchmark::measure(&blk)
438    end
439
440    #
441    # An in-place version of #add.
442    #
443    def add!(&blk)
444      t = Benchmark::measure(&blk)
445      @utime  = utime + t.utime
446      @stime  = stime + t.stime
447      @cutime = cutime + t.cutime
448      @cstime = cstime + t.cstime
449      @real   = real + t.real
450      self
451    end
452
453    #
454    # Returns a new Tms object obtained by memberwise summation
455    # of the individual times for this Tms object with those of the other
456    # Tms object.
457    # This method and #/() are useful for taking statistics.
458    #
459    def +(other); memberwise(:+, other) end
460
461    #
462    # Returns a new Tms object obtained by memberwise subtraction
463    # of the individual times for the other Tms object from those of this
464    # Tms object.
465    #
466    def -(other); memberwise(:-, other) end
467
468    #
469    # Returns a new Tms object obtained by memberwise multiplication
470    # of the individual times for this Tms object by _x_.
471    #
472    def *(x); memberwise(:*, x) end
473
474    #
475    # Returns a new Tms object obtained by memberwise division
476    # of the individual times for this Tms object by _x_.
477    # This method and #+() are useful for taking statistics.
478    #
479    def /(x); memberwise(:/, x) end
480
481    #
482    # Returns the contents of this Tms object as
483    # a formatted string, according to a format string
484    # like that passed to Kernel.format. In addition, #format
485    # accepts the following extensions:
486    #
487    # <tt>%u</tt>::     Replaced by the user CPU time, as reported by Tms#utime.
488    # <tt>%y</tt>::     Replaced by the system CPU time, as reported by #stime (Mnemonic: y of "s*y*stem")
489    # <tt>%U</tt>::     Replaced by the children's user CPU time, as reported by Tms#cutime
490    # <tt>%Y</tt>::     Replaced by the children's system CPU time, as reported by Tms#cstime
491    # <tt>%t</tt>::     Replaced by the total CPU time, as reported by Tms#total
492    # <tt>%r</tt>::     Replaced by the elapsed real time, as reported by Tms#real
493    # <tt>%n</tt>::     Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame")
494    #
495    # If _fmtstr_ is not given, FMTSTR is used as default value, detailing the
496    # user, system and real elapsed time.
497    #
498    def format(arg0 = nil, *args)
499      fmtstr = (arg0 || FMTSTR).dup
500      fmtstr.gsub!(/(%[-+\.\d]*)n/){"#{$1}s" % label}
501      fmtstr.gsub!(/(%[-+\.\d]*)u/){"#{$1}f" % utime}
502      fmtstr.gsub!(/(%[-+\.\d]*)y/){"#{$1}f" % stime}
503      fmtstr.gsub!(/(%[-+\.\d]*)U/){"#{$1}f" % cutime}
504      fmtstr.gsub!(/(%[-+\.\d]*)Y/){"#{$1}f" % cstime}
505      fmtstr.gsub!(/(%[-+\.\d]*)t/){"#{$1}f" % total}
506      fmtstr.gsub!(/(%[-+\.\d]*)r/){"(#{$1}f)" % real}
507      arg0 ? Kernel::format(fmtstr, *args) : fmtstr
508    end
509
510    #
511    # Same as #format.
512    #
513    def to_s
514      format
515    end
516
517    #
518    # Returns a new 6-element array, consisting of the
519    # label, user CPU time, system CPU time, children's
520    # user CPU time, children's system CPU time and elapsed
521    # real time.
522    #
523    def to_a
524      [@label, @utime, @stime, @cutime, @cstime, @real]
525    end
526
527    protected
528    def memberwise(op, x)
529      case x
530      when Benchmark::Tms
531        Benchmark::Tms.new(utime.__send__(op, x.utime),
532                           stime.__send__(op, x.stime),
533                           cutime.__send__(op, x.cutime),
534                           cstime.__send__(op, x.cstime),
535                           real.__send__(op, x.real)
536                           )
537      else
538        Benchmark::Tms.new(utime.__send__(op, x),
539                           stime.__send__(op, x),
540                           cutime.__send__(op, x),
541                           cstime.__send__(op, x),
542                           real.__send__(op, x)
543                           )
544      end
545    end
546  end
547
548  # The default caption string (heading above the output times).
549  CAPTION = Benchmark::Tms::CAPTION
550
551  # The default format string used to display times.  See also Benchmark::Tms#format.
552  FMTSTR = Benchmark::Tms::FMTSTR
553end
554
555if __FILE__ == $0
556  include Benchmark
557
558  n = ARGV[0].to_i.nonzero? || 50000
559  puts %Q([#{n} times iterations of `a = "1"'])
560  benchmark("       " + CAPTION, 7, FMTSTR) do |x|
561    x.report("for:")   {for i in 1..n; a = "1"; end} # Benchmark::measure
562    x.report("times:") {n.times do   ; a = "1"; end}
563    x.report("upto:")  {1.upto(n) do ; a = "1"; end}
564  end
565
566  benchmark do
567    [
568      measure{for i in 1..n; a = "1"; end},  # Benchmark::measure
569      measure{n.times do   ; a = "1"; end},
570      measure{1.upto(n) do ; a = "1"; end}
571    ]
572  end
573end