benchmark-ips

repository·master·Indexed 23 days ago

https://github.com/evanphx/benchmark-ips

An enhancement to the standard Ruby Benchmark library that measures iterations per second (i/s). It automatically determines the optimal number of iterations to provide statistically significant data. Features include multiple reporting modes (typical, iteration-controlled, and string-grafted) to reduce overhead, custom suites for Garbage Collection control, bootstrap confidence intervals via the kalibera gem, and the ability to export results to JSON or share them online via benchmark.fyi.

Tokens
4.6K
Snippets
10
Records
29
Agent score
82%

What's inside benchmark-ips

  1. How to reduce benchmark overhead

    master

    When benchmarking extremely small workloads, the overhead of the benchmark loop itself can introduce errors. There are two ways to mitigate this:

    1. Manual Iteration Loop: Pass a block that accepts a times argument. You must run your code exactly times times inside a loop. This is the recommended approach for reducing overhead.
    2. Code Grafting: Pass the code as a string as the second argument to x.report. This grafts the code directly into the internal loop. This is typically not needed if you use the |times| form.
    # Method 1: Manual loop (Recommended)
    x.report("addition2") do |times|
      i = 0
      while i < times
        i += 1
        1 + 2
      end
    end
    
    # Method 2: Code grafting
    x.report("addition3", "1 + 2")
  2. Basic usage of Benchmark.ips

    master

    The primary way to use benchmark-ips is via the Benchmark.ips block. Inside the block, you can configure the benchmark and report different code snippets. Use x.compare! at the end of the block to see a comparison of the iterations per second (i/s) between all reported snippets.

    require 'benchmark/ips'
    
    Benchmark.ips do |x|
      # Configure the number of seconds used during
      # the warmup phase (default 2) and calculation phase (default 5)
      x.config(warmup: 2, time: 5)
    
      # Typical mode, runs the block as many times as it can
      x.report("addition") { 1 + 2 }
    
      # To reduce overhead, the number of iterations is passed in
      # and the block must run the code the specific number of times.
      x.report("addition2") do |times|
        i = 0
        while i < times
          i += 1
          1 + 2
        end
      end
    
      # To reduce overhead even more, grafts the code given into
      # the loop that performs the iterations internally to reduce
      # overhead.
      x.report("addition3", "1 + 2")
    
      # Compare the iterations per second of the various reports!
      x.compare!
    end
  3. Generate a formatted string representation of a benchmark entry

    master

    The Benchmark::IPS::Report::Entry#to_s method (and its display method for printing to $stdout) produces a formatted string containing the label, iterations per second, error percentage, and iteration count.

    The format changes based on the global Benchmark::IPS.options[:format] setting:

    1. :human format: Uses scaled values for readability (e.g., 1.2k i/s) and includes the error percentage (e.g., ±4.1%).
    2. Default format: Provides a fixed-width numeric representation.

    If show_total_time! has been called on the entry, the total runtime in seconds will be appended to the output.

  4. Configure advanced bootstrap statistics

    master

    By default, benchmark-ips shows a margin of error based on one standard deviation. For more mathematically sound results, you can use a bootstrap confidence interval. This requires the kalibera gem to be installed (gem install kalibera).

    When using :bootstrap, the report uses the median of the interval rather than the mean.

    # Requires 'gem install kalibera'
    Benchmark.ips do |x|
      x.config(:stats => :bootstrap, :confidence => 95)
      # or
      x.stats = :bootstrap
      x.confidence = 95
    end
  5. Configure benchmark iterations and suites

    master

    You can customize the execution behavior using x.config or direct attribute assignment within the Benchmark.ips block:

    • warmup: Seconds used during the warmup phase (default 2).
    • time: Seconds used during the calculation phase (default 5).
    • iterations: Number of times to run the warmup and calculation stages. Useful for Ruby implementations that optimize using tracing/on-stack-replacement. Default is 1.
    • suite: A custom suite object to control behavior like Garbage Collection.
  6. Use Benchmark.ips to measure code performance

    master

    The primary way to use benchmark-ips is via the Benchmark.ips block. Inside the block, you can configure the benchmark duration and warmup time, and then use x.report to define the code snippets you want to measure. To compare the results of multiple reports, call x.compare! at the end of the block.

    Configuration Options

    • time: The number of seconds to run the calculation phase (default is 5).
    • warmup: The number of seconds to run the warmup phase (default is 2).
    • quiet: Boolean to suppress output.

    Reporting Modes

    • Typical mode: x.report("label") { code } runs the block as many times as possible.
    • Iteration-controlled mode: x.report("label") { |times| ... } is used for very small workloads to reduce overhead. The block must run the code exactly times number of times.
    • String-grafted mode: x.report("label", "code_string") grafts the code into the internal loop to further reduce overhead.
    require 'benchmark/ips'
    
    Benchmark.ips do |x|
      # Configure duration and warmup
      x.config(:time => 5, :warmup => 2)
    
      # Typical mode
      x.report("addition") { 1 + 2 }
    
      # Iteration-controlled mode (for small workloads)
      x.report("addition2") do |times|
        i = 0
        while i < times
          1 + 2
          i += 1
        end
      end
    
      # String-grafted mode (lowest overhead)
      x.report("addition3", "1 + 2")
    
      # Compare results
      x.compare!
    end
  7. Use ips_quick for simple comparisons

    master
    For quick, low-overhead comparisons where you don't need fine-grained control, use Benchmark.ips_quick. This is useful for comparing methods on objects or simple method calls, but note that it may understate differences for extremely fast microbenchmarks (over 1 million i/s).
  8. Use custom suites to control Garbage Collection

    master

    You can pass a custom suite object to x.config(:suite => suite) to manage environment states, such as enabling/disabling Garbage Collection between runs.

    class GCSuite
      def warming(*); run_gc; end
      def running(*); run_gc; end
      def warmup_stats(*); end
      def add_report(*); end
    
      private
      def run_gc
        GC.enable
        GC.start
        GC.disable
      end
    end
    
    suite = GCSuite.new
    
    Benchmark.ips do |x|
      x.config(:suite => suite)
      x.report("job1") { ... }
    end
  9. Run independent benchmarks with hold! and save!

    master

    If you want to ensure measurements are independent (e.g., to avoid cross-contamination between different Ruby invocations), use these commands:

    • x.hold! 'filename': Runs only one benchmark each time you run the command, storing results in the specified file. The file is deleted once all results are gathered and the report is shown.
    • x.save!: An alternative command for saving results (see examples/save.rb in the repo for details).
    Benchmark.ips do |x|
      x.hold! 'filename'
    end
  10. Export benchmark results to JSON

    master

    You can output results in JSON format for use with other tools.

    • To save to a file: x.json! 'filename.json'
    • To output to STDOUT: Set x.quiet = true and then call x.json! STDOUT.
    Benchmark.ips do |x|
      x.report("some report") {  }
      x.json! 'filename.json'
    end
    
    # To STDOUT
    Benchmark.ips do |x|
      x.report("some report") {  }
      x.quiet = true
      x.json! STDOUT
    end