lib/benchmark/inputs.rb in benchmark-inputs-1.0.1 vs lib/benchmark/inputs.rb in benchmark-inputs-1.1.0
- old
+ new
@@ -1,25 +1,47 @@
-require 'benchmark/inputs/version'
+require "benchmark/inputs/version"
module Benchmark
# Initializes a benchmark job with the given inputs and yields that
# job to the given block.
#
- # Example:
- # Benchmark.inputs(['abc', 'aaa', 'xyz', '']) do |job|
- # job.report('String#tr'){|s| s.tr('a', 'A') }
- # job.report('String#gsub'){|s| s.gsub(/a/, 'A') }
+ # @example Benchmarking non-destructive operations
+ # Benchmark.inputs(["abc", "aaa", "xyz", ""]) do |job|
+ # job.report("String#tr"){|s| s.tr("a", "A") }
+ # job.report("String#gsub"){|s| s.gsub(/a/, "A") }
# job.compare!
# end
#
- # @param [Array] vals input values to yield to each benchmark action
- # @yield [job] configures job and runs benchmarks
- # @yieldparam [Benchmark::Inputs::Job] job benchmark runner
- # @return [Benchmark::Inputs::Job] benchmark runner
- def self.inputs(vals)
- job = Inputs::Job.new(vals)
+ # @example Benchmarking destructive operations
+ # Benchmark.inputs(["abc", "aaa", "xyz", ""], dup_inputs: true) do |job|
+ # job.report("String#tr!"){|s| s.tr!("a", "A") }
+ # job.report("String#gsub!"){|s| s.gsub!(/a/, "A") }
+ # job.compare!
+ # end
+ #
+ # @param values [Array]
+ # input values to yield to each benchmark action
+ # @param options [Hash]
+ # @option options :dup_inputs [Boolean]
+ # whether input values will be +dup+-ed before they are passed to a
+ # {Inputs::Job#report} block
+ # @option options :sample_n [Integer]
+ # number of samples to take when benchmarking
+ # @option options :sample_dt [Integer]
+ # approximate duration of time (in nanoseconds) each sample should
+ # take when benchmarking
+ # @yield [job]
+ # configures job and runs benchmarks
+ # @yieldparam job [Benchmark::Inputs::Job]
+ # benchmark runner
+ # @return [Benchmark::Inputs::Job]
+ # benchmark runner
+ # @raise [ArgumentError]
+ # if +values+ is empty
+ def self.inputs(values, **options)
+ job = Inputs::Job.new(values, options)
yield job
job
end
@@ -27,50 +49,82 @@
NS_PER_S = 1_000_000_000
NS_PER_MS = NS_PER_S / 1_000
class Job
- attr_accessor :sample_n, :sample_dt
- attr_reader :dup_inputs, :reports
- def initialize(inputs)
+ # @param inputs [Array]
+ # input values to yield to each benchmark action
+ # @param dup_inputs [Boolean]
+ # whether input values will be +dup+-ed before they are passed
+ # to a {report} block
+ # @param sample_n [Integer]
+ # number of samples to take when benchmarking
+ # @param sample_dt [Integer]
+ # approximate duration of time (in nanoseconds) each sample
+ # should take when benchmarking
+ # @raise [ArgumentError]
+ # if +inputs+ is empty
+ def initialize(inputs, dup_inputs: false, sample_n: 10, sample_dt: NS_PER_MS * 200)
+ raise ArgumentError, "No inputs specified" if inputs.empty?
+
@inputs = inputs
- @dup_inputs = false
- @sample_n = 10
- @sample_dt = NS_PER_MS * 200
+ @dup_inputs = dup_inputs
+ @sample_n = sample_n
+ @sample_dt = sample_dt
@reports = []
def_bench!
end
- # Sets the +dup_inputs+ flag. If set to true, causes input values
- # to be +dup+'d before they are passed to a +report+ block. This
- # is necessary when +report+ blocks destructively modify their
+ # Indicates whether input values will be +dup+-ed before they are
+ # passed to a {report} block. Defaults to +false+. This should
+ # be set to +true+ if {report} blocks destructively modify their
# arguments.
#
- # Example:
- # Benchmark.inputs(['abc', 'aaa', 'xyz', '']) do |job|
- # job.dup_inputs = true # <---
- # job.report('String#tr!'){|s| s.tr!('a', 'A') }
- # job.report('String#gsub!'){|s| s.gsub!(/a/, 'A') }
- # job.compare!
- # end
+ # @return [Boolean]
+ attr_reader :dup_inputs
+
+ # See {dup_inputs}.
#
- # @param [Boolean] val value to set
- def dup_inputs=(val)
- @dup_inputs = val
+ # @param flag [Boolean]
+ # @return [Boolean]
+ def dup_inputs=(flag)
+ @dup_inputs = flag
def_bench!
@dup_inputs
end
- # Benchmarks the given block using the initially provided input
- # values. If +#dup_inputs+ is set to true, each input value is
- # +dup+'d before being passed to the block. Afterwards, the
- # block's invocations per second (i/s) is printed to +$stdout+.
+ # The number of samples to take when benchmarking. Defaults to 10.
#
- # @param [String] label label for the benchmark
- # @yield [input] action to benchmark
- # @yieldparam input one of the initially provided input values
+ # @return [Integer]
+ attr_accessor :sample_n
+
+ # The approximate duration of time (in nanoseconds) each sample
+ # should take when benchmarking. Defaults to 200,000 nanoseconds.
+ #
+ # @return [Integer]
+ attr_accessor :sample_dt
+
+ # Array of benchmark reports. Each call to {report} adds an
+ # element to this array.
+ #
+ # @return [Array<Benchmark::Inputs::Report>]
+ attr_reader :reports
+
+ # Benchmarks the given block using the previously provided input
+ # values. If {dup_inputs} is set to true, each input value is
+ # +dup+'d before being passed to the block. The block's
+ # iterations per second (i/s) is printed to +$stdout+, and a
+ # {Report} is added to {reports}.
+ #
+ # @param label [String]
+ # label for the benchmark
+ # @yield [input]
+ # action to benchmark
+ # @yieldparam input [Object]
+ # one of the initially provided input values
+ # @return [void]
def report(label)
# estimate repititions
reps = 1
reps_time = 0
while reps_time < @sample_dt
@@ -89,17 +143,18 @@
end
$stdout.puts(r.label)
$stdout.printf(" %.1f i/s (\u00B1%.2f%%)\n", r.ips, r.stddev / r.ips * 100)
@reports << r
- r
end
# Prints the relative speeds (from fastest to slowest) of all
- # +#report+ed benchmarks to +$stdout+.
+ # {report}-ed benchmarks to +$stdout+.
+ #
+ # @return [void]
def compare!
- return $stdout.puts('Nothing to compare!') if @reports.empty?
+ return $stdout.puts("Nothing to compare!") if @reports.empty?
@reports.sort_by!{|r| -r.ips }
@reports.each{|r| r.slower_than!(@reports.first) }
max_label_len = @reports.map{|r| r.label.length }.max
@@ -107,13 +162,13 @@
$stdout.puts("\nComparison:")
@reports.each_with_index do |r, i|
$stdout.printf(format, r.label, r.ips)
if r.ratio
- $stdout.printf(' - %.2fx slower', r.ratio)
+ $stdout.printf(" - %.2fx slower", r.ratio)
elsif i > 0
- $stdout.printf(' - same-ish: difference falls within error')
+ $stdout.printf(" - same-ish: difference falls within error")
end
$stdout.puts
end
$stdout.puts
end
@@ -121,16 +176,15 @@
private
def def_bench!
assigns = @inputs.each_index.map do |i|
"x#{i} = @inputs[#{i}]"
- end.join(';')
+ end.join(";")
- yields = @inputs.each_with_index.map do |x, i|
- dup = (@dup_inputs && x.respond_to?(:dup)) ? '.dup' : ''
- "yield(x#{i}#{dup})"
- end.join(';')
+ yields = @inputs.each_index.map do |i|
+ dup_inputs ? "yield(x#{i}.dup)" : "yield(x#{i})"
+ end.join(";")
code = <<-CODE
def bench(reps)
#{assigns}
i = reps
@@ -149,22 +203,36 @@
end
end
class Report
- attr_reader :label, :ratio
+ # The label for the report.
+ #
+ # @return [String]
+ attr_reader :label
+ # The ratio of iterations per second for this report compared to
+ # the fastest report. Will be +nil+ if the difference between the
+ # two falls within the combined measurement error.
+ #
+ # This value is set by {Benchmark::Inputs::Job#compare!}.
+ #
+ # @return [Float, nil]
+ attr_reader :ratio
+
+ # @!visibility private
def initialize(label, invocs_per_sample)
@label = label.to_s
@invocs_per_sample = invocs_per_sample.to_f
@ratio = nil
@n = 0
@mean = 0.0
@m2 = 0.0
end
+ # @!visibility private
def add_sample(time_ns)
sample_ips = @invocs_per_sample * NS_PER_S / time_ns
# see https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
# or Knuth's TAOCP vol 2, 3rd edition, page 232
@@ -173,21 +241,29 @@
@mean += delta / @n
@m2 += delta * (sample_ips - @mean)
@stddev = nil
end
+ # The estimated iterations per second for the report.
+ #
+ # @return [Float]
def ips
@mean
end
+ # The {ips} standard deviation.
+ #
+ # @return [Float]
def stddev
@stddev ||= @n < 2 ? 0.0 : Math.sqrt(@m2 / (@n - 1))
end
+ # @!visibility private
def slower_than!(faster)
@ratio = overlap?(faster) ? nil : (faster.ips / self.ips)
end
+ # @!visibility private
def overlap?(faster)
(faster.ips - faster.stddev) <= (self.ips + self.stddev)
end
end