lib/stud/benchmark.rb in stud-0.0.8 vs lib/stud/benchmark.rb in stud-0.0.10
- old
+ new
@@ -1,31 +1,60 @@
# encoding: UTF-8
# Benchmark Use Cases
# * Compare performance of different implementations.
# * run each implementation N times, compare runtimes (histogram, etc)
+require "metriks"
+require "stud/benchmark/rusage"
+
module Stud
module Benchmark
def self.run(iterations=1, &block)
- i = 0
- data = []
- full_start = Time.now
- while i < iterations
- start = Time.now
- block.call
- duration = Time.now - start
- data << duration
- i += 1
- end
- return Results.new(data)
+ timer = Metriks::Timer.new
+ iterations.times { timer.time(&block) }
+ return Results.new(timer)
end # def run
+ def self.runtimed(seconds=10, &block)
+ timer = Metriks::Timer.new
+ expiration = Time.now + seconds
+ timer.time(&block) while Time.now < expiration
+ return Results.new(timer)
+ end # def runtimed
+
+ def self.cputimed(seconds=10, &block)
+ timer = Metriks::Timer.new
+ expiration = Time.now + seconds
+ while Time.now < expiration
+ start = Stud::Benchmark::RUsage.get
+ block.call
+ finish = Stud::Benchmark::RUsage.get
+ cputime = (finish.user + finish.system) - (start.user + start.system)
+ timer.update(cputime)
+ end # while not expired
+ return Results.new(timer)
+ end # self.cpu
+
class Results
include Enumerable
# Stolen from https://github.com/holman/spark/blob/master/spark
- TICKS = %w{▁ ▂ ▃ ▄ ▅ ▆ ▇ █}
+ # TICKS = %w{▁ ▂ ▃ ▄ ▅ ▆ ▇ █}
+ TICKS = ["\x1b[38;5;#{232 + 8}m_\x1b[0m"] + %w{▁ ▂ ▃ ▄ ▅ ▆ ▇ █}
+
+ #.collect do |tick|
+ # 256 color support, use grayscale
+ #1.times.collect do |shade|
+ # '38' is foreground
+ # '48' is background
+ # Grey colors start at 232, but let's use the brighter half.
+ # escape [ 38 ; 5 ; <color>
+ #"\x1b[38;5;#{232 + 12 + 2 * shade}m#{tick}\x1b[0m"
+ #end
+ #tick
+ #end.flatten
+
def initialize(data)
@data = data
end # def initialize
def environment
@@ -37,88 +66,53 @@
return "#{engine} #{version}"
end # def environment
def each(&block)
- @data.each(&block)
+ @data.snapshot.each(&block)
end # def each
- def log_distribution
- return distribution do |value|
- if value == 0
- 0 ... 0
- else
- tick = (Math.log2(value).floor).to_f rescue 0
- (2 ** tick) ... (2 ** (tick+1))
- end
- end
- end # def log_distribution
+ def min
+ return @data.min
+ end
- def distribution(&range_compute)
- raise ArgumentError.new("Missing range computation block") if !block_given?
+ def max
+ return @data.max
+ end
- max = @data.max
- dist = Hash.new { |h,k| h[k] = 0 }
- each do |value|
- range = range_compute.call(value)
- dist[range] += 1
- end
- return dist
- end # def distribution
-
def mean
- if @mean.nil?
- total = Float(@data.count)
- @mean = sum / total
- end
- return @mean
+ return @data.mean
end # def mean
def stddev
- # sum of square deviations of mean divided by total values
- return Math.sqrt(inject(0) { |s, v| s + (v - mean) ** 2 } / (@data.count - 1))
+ # work around (Timer#stddev reports the variance)
+ # https://github.com/eric/metriks/pull/29
+ return @data.stddev ** 0.5
end # def stddev
def sum
- if @sum.nil?
- @sum = inject(0) { |s,v| s + v }
- end
- return @sum
+ return @data.instance_eval { @histogram.sum }
end # def sum
- def to_s
- return "#{environment}: avg: #{mean} stddev: #{stddev}"
- end # def to_s
-
def pretty_print
- min = @data.min
- max = @data.max
- zmax = Float(max - min) # "zero" at the 'min' value, offset the max.
- incr = 0.1 # 10% increments
- #dist = distribution do |value|
- #percent = (value - min) / zmax
- #if percent == 1
- #(1 - incr ... 1.0)
- #else
- #start = ((percent * 10).floor / 10.0)
- #start ... (start + incr)
- #end
- #end
- dist = log_distribution
+ puts self
+ end # def pretty_print
- total = dist.inject(0) { |sum, (step, count)| sum + count }
- sorted = dist.sort { |a,b| a.first.begin <=> b.first.begin }
- puts sorted.collect { |lower_bound, count|
- #puts lower_bound
- percent = (count / Float(total))
- "%40s: %s" % [lower_bound, (TICKS.last * (50 * percent).ceil)]
- }.join("\n")
+ def to_s(scale=min .. max, ticks=10)
+ snapshot = @data.snapshot
+ values = snapshot.instance_eval { @values }
+ scale_distance = scale.end - scale.begin
+ tick = scale_distance / ticks
+ dist = ticks.to_i.times.collect do |i|
+ range = (scale.begin + tick * i) ... (scale.begin + tick * (i+1))
+ hits = values.select { |v| range.include?(v) }.count
+ percent = hits / values.size.to_f
+ next TICKS[(TICKS.count * percent).ceil] || TICKS.last
+ end
- end # def pretty_print
+ return sprintf("%20s %s (%.4f ... %.4f, mean: %0.4f, stddev: %0.4f)",
+ environment, dist.join(""), scale.begin, scale.end,
+ mean, stddev)
+ end # def to_s
end # class Stud::Benchmark::Result
end # module Benchmark
end # module Stud
-
-#require "thread"
-#mutex = Mutex.new
-#results = Stud::Benchmark.run(20) { mutex.synchronize { rand; rand; rand } }
-#results.pretty_print