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