class BenchmarkDriver::Output::Compare

Compare output like benchmark-ips

Constants

NAME_LENGTH
Result

Public Class Methods

new(metrics:, jobs:, contexts:) click to toggle source

@param [Array<BenchmarkDriver::Metric>] metrics @param [Array<BenchmarkDriver::Job>] jobs @param [Array<BenchmarkDriver::Context>] contexts

# File lib/benchmark_driver/output/compare.rb, line 8
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

Public Instance Methods

report(result) click to toggle source

@param [BenchmarkDriver::Result] result

# File lib/benchmark_driver/output/compare.rb, line 81
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
with_benchmark(&block) click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 23
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
with_context(context, &block) click to toggle source

@param [BenchmarkDriver::Context] context

# File lib/benchmark_driver/output/compare.rb, line 74
def with_context(context, &block)
  @context = context
  @job_contexts << context
  block.call
end
with_job(job, &block) click to toggle source

@param [BenchmarkDriver::Job] job

# File lib/benchmark_driver/output/compare.rb, line 49
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
with_warmup(&block) click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 15
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

Private Instance Methods

compare_executables() click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 172
def compare_executables
  $stdout.puts "\nComparison:"

  @job_context_result.each do |job, context_result|
    $stdout.puts("%#{@name_length + 2 + 11}s" % 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
compare_jobs() click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 164
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
estimate_clock(sec, iter) click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 158
def estimate_clock sec, iter
  hz = File.read('/proc/cpuinfo').scan(/cpu MHz\s+:\s+([\d\.]+)/){|(f)| break hz = Rational(f.to_f) * 1_000_000}
  r = Rational(sec, iter)
  Integer(r/(1/hz))
end
humanize(value, width = 10) click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 118
def humanize(value, width = 10)
  if BenchmarkDriver::Result::ERROR.equal?(value)
    return " %#{width}s" % 'ERROR'
  elsif value == 0.0
    return " %#{width}.3f" % 0.0
  elsif value < 0
    raise ArgumentError.new("Negative value: #{value.inspect}")
  end

  scale = (Math.log10(value) / 3).to_i
  return "%#{width}s" % value.to_s if scale < 0 # like 1.23e-04

  prefix = "%#{width}.3f" % (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
pretty_sec(sec, iter) click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 144
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
show_durations() click to toggle source
# File lib/benchmark_driver/output/compare.rb, line 92
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)) < 1_000
      $stdout.print(" (#{pretty_sec(sec, iter)}/i, #{clks}clocks/i)")
    else
      $stdout.print(" (#{pretty_sec(sec, iter)}/i)")
    end
  end
end
show_results(results, show_context:) click to toggle source

@param [Array<BenchmarkDriver::Output::Compare::Result>] results @param [TrueClass,FalseClass] show_context

# File lib/benchmark_driver/output/compare.rb, line 186
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|
    if result != first
      if @metrics.first.larger_better
        ratio = (first.value / result.value)
      else
        ratio = (result.value / first.value)
      end
      slower = "- %.2fx  #{@metrics.first.worse_word}" % ratio
    end
    if show_context
      name = result.context.name
    else
      name = result.job
    end
    $stdout.puts("%#{@name_length}s: %11.1f %s #{slower}" % [name, result.value, @metrics.first.unit])
  end
  $stdout.puts
end
without_stdout_buffering() { || ... } click to toggle source

benchmark_driver ouputs logs ASAP. This enables sync flag for it.

# File lib/benchmark_driver/output/compare.rb, line 111
def without_stdout_buffering
  sync, $stdout.sync = $stdout.sync, true
  yield
ensure
  $stdout.sync = sync
end