Class: BenchRb

Inherits:
Object show all
Defined in:
lib/benchrb.rb

Overview

BenchRb is a simple wrapper around Ruby’s Benchmark module which allows easily running a benchmark N times and getting averages, min, max, etc.

Defined Under Namespace

Classes: Console

Constant Summary collapse

VERSION =

Gem version

'1.0.0'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBenchRb

Create a new BenchRb instance for recording results.



227
228
229
230
231
232
233
234
235
# File 'lib/benchrb.rb', line 227

def initialize
  @map = %w{user system total real}
  @min = [0,0,0,0]
  @max = [0,0,0,0]
  @avg = [0,0,0,0]
  @tot = [0,0,0,0]
  @count = 0
  @value = nil
end

Instance Attribute Details

#valueObject

Returns the value of attribute value.



222
223
224
# File 'lib/benchrb.rb', line 222

def value
  @value
end

Class Method Details

.bench(count = nil, &block) ⇒ Object

Benchmark a block of code with a given count. Count defaults to 1. Returns a BenchRb instance.



127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/benchrb.rb', line 127

def self.bench count=nil, &block
  count  = 1 if !count || count < 1
  result = new

  GC.start

  count.times do
    bm = Benchmark.measure(&block)
    result.add [bm.utime, bm.stime, bm.total, bm.real]
  end

  return result
end

.console(opts = {}) ⇒ Object

Interactive console mode. Takes the same options as BenchRb.run.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/benchrb.rb', line 145

def self.console opts={}
  help = [
    "`exit'   to quit",
    "`help'   for help screen",
    "`pause'  to stop benchmarking temporarily",
    "`bench'  to start benchmarking again",
    "`-n NUM' to change the benchmark count",
    "End lines with `\\' to run multiple lines"
  ]

  trap(:INT){ puts "\n"; exit 1 }

  $stderr.puts help.join("\n")
  $stderr.puts "\n"

  console = Console.new :prompt => "bench> "
  paused  = false

  ruby = ""

  loop do
    str = console.read_line.strip

    case str
    when ""
      next

    when "exit"
      break

    when "help"
      $stderr.puts help.join("\n")
      $stderr.puts "\n"
      next

    when "pause"
      paused = true
      console.prompt = "ruby> "
      next

    when "bench"
      paused = false
      console.prompt = "bench> "
      next

    when /^-n *(\d+)$/
      opts[:count] = $1.to_i
      puts "Count set to #{$1}"
      next

    else
      ruby << str
    end

    ruby[-1] = "\n" and next if ruby[-1] == "\\"

    res = begin
      if paused
        "=> #{eval(ruby, opts[:binding] || $benchrb_binding).inspect}"
      else
        run(ruby, opts).inspect
      end

    rescue SystemExit, SignalException
      raise

    rescue Exception => err
      "#{err.class}: #{err.message}\n#{err.backtrace.map{|b| "\tfrom #{b}"}.
        join("\n")}"
    end

    ruby = ""
    puts res
  end
end

.parse_args(argv) ⇒ Object

Parse command line arguments.



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
# File 'lib/benchrb.rb', line 20

def self.parse_args argv
  require 'optparse'

  options = {}

  opts = OptionParser.new do |opt|
      opt.program_name = File.basename $0
      opt.version = BenchRb::VERSION
      opt.release = nil

      opt.banner = <<-STR

#{opt.program_name} #{opt.version}

Quickly benchmark ruby code.

Usage:
  #{opt.program_name} --help
  #{opt.program_name} --version
  #{opt.program_name} [options] [ruby code]

Examples:
  #{opt.program_name} "[0, 1, 2, 3].inject(1){|last, num| last + num}"
  #{opt.program_name} -n 1000000 "2**10"
 

Running without ruby code argument will open an interactive shell.

Options:
    STR

    opt.on('-n', '--number INT', Integer,
    'Number of times to run the code (default 10000)') do |count|
      options[:count] = count
    end

    opt.on('-r MODULE', String, 'Same as `ruby -r\'') do |lib|
      require lib
    end

    opt.on('-I PATH', String, 'Specify $LOAD_PATH directory') do |path|
      $:.unshift path
    end

    opt.on('-h', '--help', 'Print this help screen') do
      puts opt
      exit
    end

    opt.on('-v', '--version', 'Output version and exit') do
      puts BenchRb::VERSION
      exit
    end
  end

  opts.parse! argv

  return [argv.last, options]
end

.run(rb_str = nil, opts = {}, &block) ⇒ Object

Run and benchmark a Ruby String or block. Returns a BenchRb instance. If no Ruby String or block is given, runs in a loop while expecting input from $stdin.

Supported options:

:binding:: The binding to run the String as (not for blocks)
:count:: The number of times to run the given code

Examples:

BenchRb.run "sleep 0.01", :count => 10

BenchRb.run :count => 10 do
  sleep 0.01
end


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/benchrb.rb', line 106

def self.run rb_str=nil, opts={}, &block
  rb_str, opts = nil, rb_str if Hash === rb_str

  if rb_str
    bind  = opts[:binding] || $benchrb_binding
    block = eval("lambda do\n#{rb_str}\nend", bind)
  end

  if block
    self.bench(opts[:count], &block)

  else
    self.console opts
  end
end

.run_cmd(argv = ARGV) ⇒ Object

Run from the command line.



84
85
86
87
# File 'lib/benchrb.rb', line 84

def self.run_cmd argv=ARGV
  res = run(*parse_args(argv))
  puts res.inspect if res
end

Instance Method Details

#add(results) ⇒ Object

Append a result. Result should be an Array of the following form:

[user_time, system_time, total_time, real_time]


242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/benchrb.rb', line 242

def add results
  if @count == 0
    @min = results.dup
    @max = results.dup
    @avg = results.dup
    @count += 1
    return self
  end

  results.each_with_index do |num, index|
    @avg[index] = ((@avg[index] * @count) + num) / (@count + 1.0)
    @min[index] = num if @min[index] > num
    @max[index] = num if @max[index] < num
    @tot[index] += num
  end

  @count += 1
  return self
end

#inspectObject

Inspect the instance. Renders the output grid as a String.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/benchrb.rb', line 266

def inspect
  grid = [
    ["   ", @map.dup],
    ["avg", @avg.map{|num| num_to_str(num)} ],
    ["min", @min.map{|num| num_to_str(num)} ],
    ["max", @max.map{|num| num_to_str(num)} ],
    ["tot", @tot.map{|num| num_to_str(num)} ]
  ]

  longest = 9
  grid.flatten.each{|item| longest = item.length if longest < item.length }

  out = ""

  grid.each do |(name, ary)|
    out << "#{name}  #{ary.map{|item| item.ljust(longest, " ")}.join(" ")}\n"
  end

  out
end

#num_to_str(num, len = 9) ⇒ Object

Turn a number into a padded String with a target length.



291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/benchrb.rb', line 291

def num_to_str num, len=9
  str = num.to_f.round(len-2).to_s
  sci = !str.index("e").nil?

  rnd = len - str.index(".") - 1
  str = num.to_f.round(rnd).to_s.ljust(len, "0") if rnd > 0 && !sci

  return str if str.length == len

  str = str.split(".", 2)[0] if !sci
  str.rjust(len, " ")
end