class BenchmarkDriver::Output::Compare
Compare output like benchmark-ips
def compare_executables
def compare_executables $stdout.puts "\nComparison:" @job_context_result.each do |job, context_result| $stdout.printf("%*s\n", @name_length + 2 + 11, job) results = context_result.flat_map do |context, result| result.values.values.map { |value| Result.new(job: job, value: value, context: context) } end show_results(results, show_context: true) end end
def compare_jobs
def compare_jobs $stdout.puts "\nComparison:" results = @job_context_result.flat_map do |job, context_result| context_result.map { |context, result| Result.new(job: job, value: result.values.values.first, context: context) } end show_results(results, show_context: false) end
def estimate_clock sec, iter
def estimate_clock sec, iter hz = nil File.read('/proc/cpuinfo').scan(/cpu MHz\s+:\s+([\d\.]+)/){|(f)| break hz = Rational(f.to_f) * 1_000_000} return unless hz r = Rational(sec, iter) Integer(r/(1/hz)) end
def humanize(value, width = 10)
def humanize(value, width = 10) if BenchmarkDriver::Result::ERROR.equal?(value) return sprintf(" %*s", width, 'ERROR') elsif value == 0.0 return sprintf(" %*.3f", width, 0.0) elsif value < 0 raise ArgumentError.new("Negative value: #{value.inspect}") end scale = (Math.log10(value) / 3).to_i return sprintf("%*s", width, value.to_s) if scale < 0 # like 1.23e-04 prefix = sprintf("%*.3f", width, (value.to_f / (1000 ** scale))) suffix = case scale when 1; 'k' when 2; 'M' when 3; 'G' when 4; 'T' when 5; 'Q' else # < 1000 or > 10^15, no scale or suffix return " #{prefix}" end "#{prefix}#{suffix}" end
def initialize(metrics:, jobs:, contexts:)
-
contexts
(Array
) -- -
jobs
(Array
) -- -
metrics
(Array
) --
def initialize(metrics:, jobs:, contexts:) @metrics = metrics @job_names = jobs.map(&:name) @context_names = contexts.map(&:name) @name_length = [@job_names.map(&:length).max, NAME_LENGTH].max end
def pretty_sec(sec, iter)
def pretty_sec(sec, iter) r = Rational(sec, iter) case when r >= 1 "#{'%3.2f' % r.to_f}s" when r >= 1/1000r "#{'%3.2f' % (r * 1_000).to_f}ms" when r >= 1/1000_000r "#{'%3.2f' % (r * 1_000_000).to_f}μs" else "#{'%3.2f' % (r * 1_000_000_000).to_f}ns" end end
def report(result)
-
result
(BenchmarkDriver::Result
) --
def report(result) @job_results << result if defined?(@job_context_result) @job_context_result[@job][@context] = result end $stdout.print("#{humanize(result.values.values.first, [10, @context.name.length].max)} ") end
def show_durations
def show_durations @job_results.each do |result| $stdout.print(' %3.6fs' % result.duration) end # Show pretty seconds / clocks too. As it takes long width, it's shown only with a single executable. if @job_results.size == 1 result = @job_results.first sec = result.duration iter = result.loop_count if File.exist?('/proc/cpuinfo') && (clks = estimate_clock(sec, iter)) && (clks < 1_000) $stdout.print(" (#{pretty_sec(sec, iter)}/i, #{clks}clocks/i)") else $stdout.print(" (#{pretty_sec(sec, iter)}/i)") end end end
def show_results(results, show_context:)
-
show_context
(TrueClass, FalseClass
) -- -
results
(Array
) --
def show_results(results, show_context:) results = results.sort_by do |result| if @metrics.first.larger_better -result.value else result.value end end first = results.first results.each do |result| slower = show_slower(first, result) if result != first if show_context name = result.context.name else name = result.job end $stdout.printf("%*s: %11.1f %s %s\n", @name_length, name, result.value, @metrics.first.unit, slower) end $stdout.puts end
def show_slower(better_result, worse_result)
def show_slower(better_result, worse_result) top = worse_result.value bottom = better_result.value top, bottom = bottom, top if @metrics.first.larger_better unless BenchmarkDriver::Result::ERROR.equal?(bottom) ratio = top / bottom sprintf("- %.2fx %s", ratio, @metrics.first.worse_word) end end
def with_benchmark(&block)
def with_benchmark(&block) @job_context_result = Hash.new do |hash, job| hash[job] = {} end result = without_stdout_buffering do $stdout.puts 'Calculating -------------------------------------' if @context_names.size > 1 $stdout.print(' ' * @name_length) @context_names.each do |context_name| $stdout.print(' %10s ' % context_name) end $stdout.puts end block.call end if @context_names.size > 1 compare_executables elsif @job_names.size > 1 compare_jobs end result end
def with_context(context, &block)
-
context
(BenchmarkDriver::Context
) --
def with_context(context, &block) @context = context @job_contexts << context block.call end
def with_job(job, &block)
-
job
(BenchmarkDriver::Job
) --
def with_job(job, &block) name = job.name if name.length > @name_length $stdout.puts(name) else $stdout.print("%#{@name_length}s" % name) end @job = name @job_results = [] @job_contexts = [] result = block.call $stdout.print(@metrics.first.unit) loop_count = @job_results.first.loop_count if loop_count && @job_results.all? { |r| r.loop_count == loop_count } $stdout.print(" - #{humanize(loop_count)} times") if @job_results.all? { |job_result| !job_result.duration.nil? } $stdout.print(" in") show_durations end end $stdout.puts result end
def with_warmup(&block)
def with_warmup(&block) without_stdout_buffering do $stdout.puts 'Warming up --------------------------------------' # TODO: show exec name if it has multiple ones block.call end end
def without_stdout_buffering
def without_stdout_buffering sync, $stdout.sync = $stdout.sync, true yield ensure $stdout.sync = sync end