GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Description: Prettier Benchmarking for Ruby
Clone URL: git://github.com/wycats/benchwarmer.git
benchwarmer / lib / benchwarmer.rb
100644 132 lines (104 sloc) 3.32 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
require "benchmark"
require File.join(File.expand_path(File.dirname(__FILE__)), "vendor", "dictionary")
 
module Enumerable
  def max_by(&blk)
    self.sort_by(&blk).last
  end
end
 
module Benchmark
  
  def self.warmer(times, &blk)
    Warmer.new(times).run(&blk)
  end
  
  class Warmer
    
    attr_reader :times, :columns, :groups
    def initialize(times)
      @times = times
    end
    
    def line!
      size = @name_max + @group_max + 1
      @columns.each do |name, val|
        size += (@columns[name].size <= 5 ? 5 : @columns[name].size) + 3
      end
      puts "-" * size
    end
    
    def blanks(size)
      print " " * size
    end
    
    def run(&blk)
      puts "Running the benchmarks #{@times} times each..."
      puts
      
      self.instance_eval(&blk)
      
      unless @columns
        @columns = Dictionary.new
        @columns[:results] = "Results"
      end
      
      @name_max = @groups.keys.max_by {|x| x.size}.size
      @group_max = @groups.values.map {|x| x.keys }.flatten.max_by {|x| x.size}.size
      
      print " " * (@name_max + @group_max + 2)
      
      puts @columns.map {|col,val| "%5s" % val }.join(" | ") + " |"
 
      line!
 
      @groups.each do |group_name,runs|
        # Print the group's name, left-justified and filling up as much space as the max
        # group name
        print "%-#{@name_max + 1}s" % group_name
 
        # Go through the registered runs
        runs.each_with_index do |(name, procs), i|
          blanks(@name_max + 1) if i > 0
          # The name has to take up all the space of the group name, and then some
          print "%#{@group_max}s" % name
          
          # Actually run the benchmarks
          procs.each_with_index do |(column, proc), i|
            head = @columns[column]
            bench = Benchmark.measure { @times.times(&proc)}
            print (" %#{[head.size, 5].max}.2f |" % bench.real)
          end
          puts
        end
        
        line!
      end
    end
    
    def columns(*list)
      @columns = list.inject(Dictionary.new) do |accum, col|
        accum[col] = col.to_s.upcase
        accum
      end
    end
    
    def titles(titles)
      @columns ||= Dictionary.new
      @columns.merge!(titles)
    end
    
    def group(str, &blk)
      @groups ||= Dictionary.new {|h,k| h[k] = Dictionary.new}
      @current_group = str
      self.instance_eval(&blk)
      @current_group = nil
    end
    
    def report(str, &blk)
      @groups ||= Dictionary.new {|h,k| h[k] = Dictionary.new}
      if !@columns || @columns.size == 1
        @groups[@current_group || ""][str] = {(@columns && @columns.order[0]) || :results => blk}
      else
        report = GroupReport.new(@columns.keys)
        report.instance_eval(&blk)
        @groups[@current_group || ""][str] = report.runs
      end
    end
    
  end
  
  class GroupReport
    self.instance_methods.each do |meth|
      send(:undef_method, meth) unless meth =~ /^(__|instance_eval)/
    end
    
    attr_accessor :runs, :cols
    
    def initialize(cols)
      new_self = (class << self; self end)
      cols.each do |col|
        new_self.class_eval <<-RUBY
def #{col}(&blk)
@runs ||= {}
@runs[#{col.to_sym.inspect}] = blk
end
RUBY
      end
    end
  end
  
end