PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/benchmark.rb

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