From f66045def35a6be5f92fe5baa61b55bb4dca4e65 Mon Sep 17 00:00:00 2001 From: Evan Phoenix Date: Sat, 7 Sep 2024 23:11:05 -0700 Subject: [PATCH] Provide Benchmark.quick_compare to quickly compare methods on an object (#134) * Provide Benchmark.quick_compare to quickly compare methods on an object * Move quick_compare to Kernel for more flexible usage * New approach * Splat methods, send all options to Job * Tests * Fix variable shadow --------- Co-authored-by: Nate Berkopec --- README.md | 12 +++++++++++ examples/quick.rb | 17 ++++++++++++++++ lib/benchmark/ips.rb | 41 ++++++++++++++++++++++++++++++++------ test/test_benchmark_ips.rb | 12 +++++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 examples/quick.rb diff --git a/README.md b/README.md index ef030de..d36ef2e 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,18 @@ One benefit to using this method is benchmark-ips automatically determines the data points for testing our code, so we can focus on the results instead of guessing iteration counts as we do with the traditional Benchmark library. +You can also use `ips_quick` to save a few lines of code: + +```ruby +Benchmark.ips_quick(:upcase, :downcase, on: "hello") # runs a suite comparing "hello".upcase and "hello".downcase + +def first; MyJob.perform(1); end +def second; MyJobOptimized.perform(1); end +Benchmark.ips_quick(:first, :second) # compares :first and :second +``` + +This adds a very small amount of overhead, which may be significant (i.e. ips_quick will understate the difference) if you're microbenchmarking things that can do over 1 million iterations per second. In that case, you're better off using the full format. + ### Custom Suite Pass a custom suite to disable garbage collection during benchmark: diff --git a/examples/quick.rb b/examples/quick.rb new file mode 100644 index 0000000..48357b5 --- /dev/null +++ b/examples/quick.rb @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +require 'benchmark/ips' + +def add + 1 + 1 +end + +def sub + 2 - 1 +end + +Benchmark.ips_quick(:add, :sub, warmup: 1, time: 1) + +h = {} + +Benchmark.ips_quick(:size, :empty?, on: h) \ No newline at end of file diff --git a/lib/benchmark/ips.rb b/lib/benchmark/ips.rb index fd038e4..fc7d424 100644 --- a/lib/benchmark/ips.rb +++ b/lib/benchmark/ips.rb @@ -13,11 +13,14 @@ # Performance benchmarking library module Benchmark # Benchmark in iterations per second, no more guessing! - # @see https://github.com/evanphx/benchmark-ips + # + # See Benchmark.ips for documentation on using this gem~ + # + # @see {https://github.com/evanphx/benchmark-ips} module IPS # Benchmark-ips Gem version. - VERSION = "2.13.0" + VERSION = "2.13.1" # CODENAME of current version. CODENAME = "Long Awaited" @@ -73,6 +76,32 @@ def ips(*args) report end + # Quickly compare multiple methods on the same object. + # @param methods [Symbol...] A list of method names (as symbols) to compare. + # @param receiver [Object] The object on which to call the methods. Defaults to Kernel. + # @param opts [Hash] Additional options for customizing the benchmark. + # @option opts [Integer] :warmup The number of seconds to warm up the benchmark. + # @option opts [Integer] :time The number of seconds to run the benchmark. + # + # @example Compare String#upcase and String#downcase + # ips_quick(:upcase, :downcase, on: "hello") + # + # @example Compare two methods you just defined, with a custom warmup. + # def add; 1+1; end + # def sub; 2-1; end + # ips_quick(:add, :sub, warmup: 10) + def ips_quick(*methods, on: Kernel, **opts) + ips(opts) do |x| + x.compare! + + methods.each do |name| + x.report(name) do |iter| + iter.times { on.__send__ name } + end + end + end + end + # Set options for running the benchmarks. # :format => [:human, :raw] # :human format narrows precision and scales results for readability @@ -83,13 +112,13 @@ def self.options module Helpers SUFFIXES = ['', 'k', 'M', 'B', 'T', 'Q'].freeze - + def scale(value) - scale = (Math.log10(value) / 3).to_i + scale = (Math.log10(value) / 3).to_i scale = 0 if scale < 0 || scale >= SUFFIXES.size suffix = SUFFIXES[scale] scaled_value = value.to_f / (1000 ** scale) - + "%10.3f#{suffix}" % scaled_value end module_function :scale @@ -109,7 +138,7 @@ def humanize_duration(duration_ns) end end - extend Benchmark::IPS # make ips available as module-level method + extend Benchmark::IPS # make ips/ips_quick available as module-level method ## # :singleton-method: ips diff --git a/test/test_benchmark_ips.rb b/test/test_benchmark_ips.rb index 4a08823..7165f17 100644 --- a/test/test_benchmark_ips.rb +++ b/test/test_benchmark_ips.rb @@ -276,4 +276,16 @@ def test_humanize_duration assert_equal Benchmark::IPS::Helpers.humanize_duration(123456789.0123456789), "123.46 ms" assert_equal Benchmark::IPS::Helpers.humanize_duration(123456789012.3456789012), "123.46 s" end + + def test_quick + Benchmark.ips_quick(:upcase, :downcase, on: "Hello World!", warmup: 0.001, time: 0.001) + + assert $stdout.string.size > 0 + end + + def test_quick_on_kernel + Benchmark.ips_quick(:srand, :rand, warmup: 0.001, time: 0.001) + + assert $stdout.string.size > 0 + end end