benchmark-ips 2.13.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc54b5cf0d24b23822486adf5759cceeefbef9a60c780454318472f4739050f6
4
- data.tar.gz: ea2764b060fef8c931c4636eb63e84d48f98242f7335648fed8b82b2fdeb053d
3
+ metadata.gz: 941e6893612b6307694098b25ebe3cc688dac27020c98cf76d603ead0bd72299
4
+ data.tar.gz: 622715bcc49a8e11c1c6285b35745b9115417e1055b8dc394ca6f5ba07186b66
5
5
  SHA512:
6
- metadata.gz: 72ed2d83e42b125ca812aa1e79113be4726f8bb2e0ca4cb784dddc1d70eddc16c088308e1214cba9f7ec83ad77d89d9ae544b7892b6abac8f3e0b88d1617e7a8
7
- data.tar.gz: 94bda95d5db4a9032692e732a04eb3147ad3839b0e674f13d676b1fdf2130a8f0a269e6f5817791683965bf86662be10c7e5476e0539d71916b6b35562ac7806
6
+ metadata.gz: b0e5c2205809523369329ea675f095dc62cefa330e6adb6005b12a6ff4f4d5cfd38ffc16fe819761e4ed10a2c41b1100af8304c3a038383d2377700436873bbb
7
+ data.tar.gz: 318217090715cb2a541606ef8ab91087aec9238ffa0d6b29d5b2b1eeebc2e5d0a725856b59ded62081f1135f2a8bb916c25a31d604c0824accaf67e46ecfab8f
data/History.md CHANGED
@@ -1,3 +1,14 @@
1
+ ### 2.14.0 / 2024-09-08
2
+
3
+ * Feature
4
+ * Adds Benchmark::IPS.quick_compare.
5
+ * Adds absolute duration of each iteration to compare output.
6
+
7
+ ### 2.13.0 / 2023-12-12
8
+
9
+ * Feature
10
+ * Prints ruby version at top of report.
11
+
1
12
  ### 2.12.0 / 2023-03-08
2
13
 
3
14
  * Feature
@@ -187,7 +198,7 @@ Add missing files
187
198
  * 1 minor fix:
188
199
  * Don't send label through printf so that % work directly
189
200
 
190
- * 1 documenation changes:
201
+ * 1 documentation changes:
191
202
  * Use HEREDOC and wrap at 80 chars for example result description
192
203
 
193
204
  * 1 usage fix:
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  * home :: https://github.com/evanphx/benchmark-ips
5
5
 
6
6
  [![Gem Version](https://badge.fury.io/rb/benchmark-ips.svg)](http://badge.fury.io/rb/benchmark-ips)
7
- [![Build Status](https://secure.travis-ci.org/evanphx/benchmark-ips.svg)](http://travis-ci.org/evanphx/benchmark-ips)
7
+ [![CI](https://github.com/evanphx/benchmark-ips/actions/workflows/ci.yml/badge.svg)](https://github.com/evanphx/benchmark-ips/actions/workflows/ci.yml)
8
8
  [![Inline docs](http://inch-ci.org/github/evanphx/benchmark-ips.svg)](http://inch-ci.org/github/evanphx/benchmark-ips)
9
9
 
10
10
  * https://github.com/evanphx/benchmark-ips
@@ -27,11 +27,7 @@ require 'benchmark/ips'
27
27
  Benchmark.ips do |x|
28
28
  # Configure the number of seconds used during
29
29
  # the warmup phase (default 2) and calculation phase (default 5)
30
- x.config(:time => 5, :warmup => 2)
31
-
32
- # These parameters can also be configured this way
33
- x.time = 5
34
- x.warmup = 2
30
+ x.config(warmup: 2, time: 5)
35
31
 
36
32
  # Typical mode, runs the block as many times as it can
37
33
  x.report("addition") { 1 + 2 }
@@ -43,8 +39,9 @@ Benchmark.ips do |x|
43
39
  x.report("addition2") do |times|
44
40
  i = 0
45
41
  while i < times
46
- 1 + 2
47
42
  i += 1
43
+
44
+ 1 + 2
48
45
  end
49
46
  end
50
47
 
@@ -64,24 +61,24 @@ end
64
61
  This will generate the following report:
65
62
 
66
63
  ```
67
- Calculating -------------------------------------
68
- addition 71.254k i/100ms
69
- addition2 68.658k i/100ms
70
- addition3 83.079k i/100ms
64
+ Warming up --------------------------------------
65
+ addition 3.572M i/100ms
66
+ addition2 3.672M i/100ms
67
+ addition3 3.677M i/100ms
71
68
  addition-test-long-label
72
- 70.129k i/100ms
73
- -------------------------------------------------
74
- addition 4.955M (± 8.7%) i/s - 24.155M
75
- addition2 24.011M9.5%) i/s - 114.246M
76
- addition3 23.958M10.1%) i/s - 115.064M
69
+ 3.511M i/100ms
70
+ Calculating -------------------------------------
71
+ addition 36.209M2.8%) i/s (27.62 ns/i) - 182.253M in 5.037433s
72
+ addition2 36.552M7.8%) i/s (27.36 ns/i) - 183.541M in 5.069987s
73
+ addition3 36.639M 4.8%) i/s (27.29 ns/i) - 182.994M in 5.009234s
77
74
  addition-test-long-label
78
- 5.014M9.1%) i/s - 24.545M
75
+ 36.164M5.8%) i/s (27.65 ns/i) - 181.312M in 5.038364s
79
76
 
80
77
  Comparison:
81
- addition2: 24011974.8 i/s
82
- addition3: 23958619.8 i/s - 1.00x slower
83
- addition-test-long-label: 5014756.0 i/s - 4.79x slower
84
- addition: 4955278.9 i/s - 4.85x slower
78
+ addition2: 36558904.5 i/s
79
+ addition3: 36359284.0 i/s - same-ish: difference falls within error
80
+ addition-test-long-label: 36135428.8 i/s - same-ish: difference falls within error
81
+ addition: 34666931.3 i/s - same-ish: difference falls within error
85
82
  ```
86
83
 
87
84
  Benchmark/ips will report the number of iterations per second for a given block
@@ -94,6 +91,18 @@ One benefit to using this method is benchmark-ips automatically determines the
94
91
  data points for testing our code, so we can focus on the results instead of
95
92
  guessing iteration counts as we do with the traditional Benchmark library.
96
93
 
94
+ You can also use `ips_quick` to save a few lines of code:
95
+
96
+ ```ruby
97
+ Benchmark.ips_quick(:upcase, :downcase, on: "hello") # runs a suite comparing "hello".upcase and "hello".downcase
98
+
99
+ def first; MyJob.perform(1); end
100
+ def second; MyJobOptimized.perform(1); end
101
+ Benchmark.ips_quick(:first, :second) # compares :first and :second
102
+ ```
103
+
104
+ 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.
105
+
97
106
  ### Custom Suite
98
107
 
99
108
  Pass a custom suite to disable garbage collection during benchmark:
data/examples/quick.rb ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'benchmark/ips'
4
+
5
+ def add
6
+ 1 + 1
7
+ end
8
+
9
+ def sub
10
+ 2 - 1
11
+ end
12
+
13
+ Benchmark.ips_quick(:add, :sub, warmup: 1, time: 1)
14
+
15
+ h = {}
16
+
17
+ Benchmark.ips_quick(:size, :empty?, on: h)
@@ -56,6 +56,8 @@ module Benchmark
56
56
  def compare(*entries, order: :fastest)
57
57
  return if entries.size < 2
58
58
 
59
+ max_width = entries.map { |e| e.label.to_s.size }.max
60
+
59
61
  case order
60
62
  when :baseline
61
63
  baseline = entries.shift
@@ -64,17 +66,17 @@ module Benchmark
64
66
  sorted = entries.sort_by{ |e| e.stats.central_tendency }.reverse
65
67
  baseline = sorted.shift
66
68
  else
67
- raise ArgumentError, "Unknwon order: #{order.inspect}"
69
+ raise ArgumentError, "Unknown order: #{order.inspect}"
68
70
  end
69
71
 
70
72
  $stdout.puts "\nComparison:"
71
73
 
72
- $stdout.printf "%20s: %10.1f i/s\n", baseline.label.to_s, baseline.stats.central_tendency
74
+ $stdout.printf "%#{max_width}s: %10.1f i/s\n", baseline.label.to_s, baseline.stats.central_tendency
73
75
 
74
76
  sorted.each do |report|
75
77
  name = report.label.to_s
76
78
 
77
- $stdout.printf "%20s: %10.1f i/s - ", name, report.stats.central_tendency
79
+ $stdout.printf "%#{max_width}s: %10.1f i/s - ", name, report.stats.central_tendency
78
80
 
79
81
  if report.stats.overlaps?(baseline.stats)
80
82
  $stdout.print "same-ish: difference falls within error"
@@ -2,9 +2,10 @@ module Benchmark
2
2
  module IPS
3
3
  class Job
4
4
  class StreamReport
5
- def initialize(stream = $stdout)
5
+ def initialize(job, stream = $stdout)
6
6
  @last_item = nil
7
7
  @out = stream
8
+ @job = job
8
9
  end
9
10
 
10
11
  def start_warming
@@ -17,8 +18,9 @@ module Benchmark
17
18
  end
18
19
 
19
20
  def warming(label, _warmup)
20
- @out.print rjust(label)
21
+ @out.print label.to_s.rjust(@job.max_width)
21
22
  end
23
+ alias_method :running, :warming
22
24
 
23
25
  def warmup_stats(_warmup_time_us, timing)
24
26
  case format
@@ -29,8 +31,6 @@ module Benchmark
29
31
  end
30
32
  end
31
33
 
32
- alias_method :running, :warming
33
-
34
34
  def add_report(item, caller)
35
35
  @out.puts " #{item.body}"
36
36
  @last_item = item
@@ -48,18 +48,6 @@ module Benchmark
48
48
  def format
49
49
  Benchmark::IPS.options[:format]
50
50
  end
51
-
52
- # Add padding to label's right if label's length < 20,
53
- # Otherwise add a new line and 20 whitespaces.
54
- # @return [String] Right justified label.
55
- def rjust(label)
56
- label = label.to_s
57
- if label.size > 20
58
- "#{label}\n#{' ' * 20}"
59
- else
60
- label.rjust(20)
61
- end
62
- end
63
51
  end
64
52
  end
65
53
  end
@@ -9,7 +9,8 @@ module Benchmark
9
9
  # The percentage of the expected runtime to allow
10
10
  # before reporting a weird runtime
11
11
  MAX_TIME_SKEW = 0.05
12
- POW_2_30 = 1 << 30
12
+ # Keep iterations below this to avoid overflow to 64-bit long or Bignum
13
+ MAX_ITERATIONS = 1 << 30
13
14
 
14
15
  # Two-element arrays, consisting of label and block pairs.
15
16
  # @return [Array<Entry>] list of entries
@@ -51,6 +52,10 @@ module Benchmark
51
52
  # @return [Integer]
52
53
  attr_accessor :confidence
53
54
 
55
+ # The maximum label width
56
+ # @return [Integer]
57
+ attr_reader :max_width
58
+
54
59
  # Silence output
55
60
  # @return [Boolean]
56
61
  def quiet
@@ -72,6 +77,7 @@ module Benchmark
72
77
  @compare_order = :fastest
73
78
  @held_path = nil
74
79
  @held_results = nil
80
+ @max_width = 20 # automatically computed as entries are added
75
81
 
76
82
  @timing = Hash.new 1 # default to 1 in case warmup isn't run
77
83
  @full_report = Report.new
@@ -85,7 +91,7 @@ module Benchmark
85
91
  @stats = :sd
86
92
  @confidence = 95
87
93
 
88
- @out = MultiReport.new(StreamReport.new)
94
+ @out = MultiReport.new(StreamReport.new(self))
89
95
  end
90
96
 
91
97
  # Job configuration options, set +@warmup+ and +@time+.
@@ -106,7 +112,7 @@ module Benchmark
106
112
  if val # remove instances of StreamReport
107
113
  @out.quiet!
108
114
  else # ensure there is an instance of StreamReport
109
- @out << StreamReport.new if @out.quiet?
115
+ @out << StreamReport.new(self) if @out.quiet?
110
116
  end
111
117
  end
112
118
 
@@ -180,6 +186,8 @@ module Benchmark
180
186
  action = str || blk
181
187
  raise ArgumentError, "no block or string" unless action
182
188
 
189
+ @max_width = label.size if label.size > @max_width
190
+
183
191
  @list.push Entry.new(label, action)
184
192
  self
185
193
  end
@@ -203,7 +211,7 @@ module Benchmark
203
211
  (after.to_f - before.to_f) * MICROSECONDS_PER_SECOND
204
212
  end
205
213
 
206
- # Calculate the interations per second given the number
214
+ # Calculate the iterations per second given the number
207
215
  # of cycles run and the time in microseconds that elapsed.
208
216
  # @param [Integer] cycles Cycles.
209
217
  # @param [Integer] time_us Time in microsecond.
@@ -219,7 +227,7 @@ module Benchmark
219
227
  JSON.load(IO.read(@held_path)).each do |result|
220
228
  @held_results[result['item']] = result
221
229
  create_report(result['item'], result['measured_us'], result['iter'],
222
- create_stats(result['samples']), result['cycles'])
230
+ create_stats(result['samples'], result['measured_us'], result['iter']), result['cycles'])
223
231
  end
224
232
  end
225
233
 
@@ -289,11 +297,13 @@ module Benchmark
289
297
 
290
298
  # If the number of cycles would go outside the 32-bit signed integers range
291
299
  # then exit the loop to avoid overflows and start the 100ms warmup runs
292
- break if cycles >= POW_2_30
300
+ break if cycles >= MAX_ITERATIONS
293
301
  cycles *= 2
294
302
  end while Timing.now + warmup_time_us * 2 < target
295
303
 
296
- cycles = cycles_per_100ms warmup_time_us, warmup_iter
304
+ per_100ms = cycles_per_100ms warmup_time_us, warmup_iter
305
+ # Not [per_100ms, MAX_ITERATIONS].min as that can promote the result to long
306
+ cycles = per_100ms > MAX_ITERATIONS ? MAX_ITERATIONS : per_100ms
297
307
  @timing[item] = cycles
298
308
 
299
309
  # Run for the remaining of warmup in a similar way as #run_benchmark.
@@ -349,7 +359,7 @@ module Benchmark
349
359
  iterations_per_sec cycles, time_us
350
360
  }
351
361
 
352
- rep = create_report(item.label, measured_us, iter, create_stats(samples), cycles)
362
+ rep = create_report(item.label, measured_us, iter, create_stats(samples, measured_us, iter), cycles)
353
363
 
354
364
  if (final_time - target).abs >= (@time.to_f * MAX_TIME_SKEW)
355
365
  rep.show_total_time!
@@ -361,10 +371,10 @@ module Benchmark
361
371
  end
362
372
  end
363
373
 
364
- def create_stats(samples)
374
+ def create_stats(samples, measured_us, iterations)
365
375
  case @stats
366
376
  when :sd
367
- Stats::SD.new(samples)
377
+ Stats::SD.new(samples, measured_us, iterations)
368
378
  when :bootstrap
369
379
  Stats::Bootstrap.new(samples, @confidence)
370
380
  else
@@ -62,7 +62,7 @@ module Benchmark
62
62
 
63
63
  # Control if the total time the job took is reported.
64
64
  # Typically this value is not significant because it's very
65
- # close to the expected time, so it's supressed by default.
65
+ # close to the expected time, so it's suppressed by default.
66
66
  def show_total_time!
67
67
  @show_total_time = true
68
68
  end
@@ -86,23 +86,33 @@ module Benchmark
86
86
  # percentage of standard deviation, iterations in runtime.
87
87
  # @return [String] Left justified body.
88
88
  def body
89
+ per_iter = (" (%s/i)" % Helpers.humanize_duration(1_000_000_000 / @stats.central_tendency)).rjust(15)
90
+
89
91
  case Benchmark::IPS.options[:format]
90
92
  when :human
91
- left = "%s (±%4.1f%%) i/s" % [Helpers.scale(@stats.central_tendency), @stats.error_percentage]
93
+ central_tendency = Helpers.scale(@stats.central_tendency)
94
+ error_percentage = @stats.error_percentage
95
+ left =
96
+ if error_percentage > 99.9
97
+ "%s (± Inf%%) i/s" % [central_tendency]
98
+ else
99
+ "%s (±%4.1f%%) i/s" % [central_tendency, @stats.error_percentage]
100
+ end.ljust(20)
101
+
92
102
  iters = Helpers.scale(@iterations)
93
103
 
94
104
  if @show_total_time
95
- left.ljust(20) + (" - %s in %10.6fs" % [iters, runtime])
105
+ left + per_iter + (" - %s in %10.6fs" % [iters, runtime])
96
106
  else
97
- left.ljust(20) + (" - %s" % iters)
107
+ left + per_iter + (" - %s" % iters)
98
108
  end
99
109
  else
100
- left = "%10.1f (±%.1f%%) i/s" % [@stats.central_tendency, @stats.error_percentage]
110
+ left = ("%10.1f (±%.1f%%) i/s" % [@stats.central_tendency, @stats.error_percentage]).ljust(20)
101
111
 
102
112
  if @show_total_time
103
- left.ljust(20) + (" - %10d in %10.6fs" % [@iterations, runtime])
113
+ left + per_iter + (" - %10d in %10.6fs" % [@iterations, runtime])
104
114
  else
105
- left.ljust(20) + (" - %10d" % @iterations)
115
+ left + per_iter + (" - %10d" % @iterations)
106
116
  end
107
117
  end
108
118
  end
@@ -113,7 +123,7 @@ module Benchmark
113
123
  @label.to_s.rjust(20)
114
124
  end
115
125
 
116
- # Return string repesentation of Entry object.
126
+ # Return string representation of Entry object.
117
127
  # @return [String] Header and body.
118
128
  def to_s
119
129
  "#{header} #{body}"
@@ -17,7 +17,7 @@ module Benchmark
17
17
  base = (ENV['SHARE_URL'] || DEFAULT_URL)
18
18
  url = URI(File.join(base, "reports"))
19
19
 
20
- req = Net::HTTP::Post.new(url)
20
+ req = Net::HTTP::Post.new(url, initheader = {'Content-Type' =>'application/json'})
21
21
 
22
22
  data = {
23
23
  "entries" => @report.data,
@@ -6,9 +6,9 @@ module Benchmark
6
6
  include StatsMetric
7
7
  attr_reader :error, :samples
8
8
 
9
- def initialize(samples)
9
+ def initialize(samples, measured_us, iterations)
10
10
  @samples = samples
11
- @mean = Timing.mean(samples)
11
+ @mean = Timing::MICROSECONDS_PER_SECOND * (iterations.to_f / measured_us)
12
12
  @error = Timing.stddev(samples, @mean).round
13
13
  end
14
14
 
data/lib/benchmark/ips.rb CHANGED
@@ -13,14 +13,17 @@ require 'benchmark/ips/job'
13
13
  # Performance benchmarking library
14
14
  module Benchmark
15
15
  # Benchmark in iterations per second, no more guessing!
16
- # @see https://github.com/evanphx/benchmark-ips
16
+ #
17
+ # See Benchmark.ips for documentation on using this gem~
18
+ #
19
+ # @see {https://github.com/evanphx/benchmark-ips}
17
20
  module IPS
18
21
 
19
22
  # Benchmark-ips Gem version.
20
- VERSION = "2.13.0"
23
+ VERSION = "2.15.0"
21
24
 
22
25
  # CODENAME of current version.
23
- CODENAME = "Long Awaited"
26
+ CODENAME = "Harmonic Nice"
24
27
 
25
28
  # Measure code in block, each code's benchmarked result will display in
26
29
  # iteration per second with standard deviation in given time.
@@ -73,6 +76,32 @@ module Benchmark
73
76
  report
74
77
  end
75
78
 
79
+ # Quickly compare multiple methods on the same object.
80
+ # @param methods [Symbol...] A list of method names (as symbols) to compare.
81
+ # @param receiver [Object] The object on which to call the methods. Defaults to Kernel.
82
+ # @param opts [Hash] Additional options for customizing the benchmark.
83
+ # @option opts [Integer] :warmup The number of seconds to warm up the benchmark.
84
+ # @option opts [Integer] :time The number of seconds to run the benchmark.
85
+ #
86
+ # @example Compare String#upcase and String#downcase
87
+ # ips_quick(:upcase, :downcase, on: "hello")
88
+ #
89
+ # @example Compare two methods you just defined, with a custom warmup.
90
+ # def add; 1+1; end
91
+ # def sub; 2-1; end
92
+ # ips_quick(:add, :sub, warmup: 10)
93
+ def ips_quick(*methods, on: Kernel, **opts)
94
+ ips(opts) do |x|
95
+ x.compare!
96
+
97
+ methods.each do |name|
98
+ x.report(name) do |iter|
99
+ iter.times { on.__send__ name }
100
+ end
101
+ end
102
+ end
103
+ end
104
+
76
105
  # Set options for running the benchmarks.
77
106
  # :format => [:human, :raw]
78
107
  # :human format narrows precision and scales results for readability
@@ -83,20 +112,33 @@ module Benchmark
83
112
 
84
113
  module Helpers
85
114
  SUFFIXES = ['', 'k', 'M', 'B', 'T', 'Q'].freeze
86
-
115
+
87
116
  def scale(value)
88
- scale = (Math.log10(value) / 3).to_i
117
+ scale = (Math.log10(value) / 3).to_i
89
118
  scale = 0 if scale < 0 || scale >= SUFFIXES.size
90
119
  suffix = SUFFIXES[scale]
91
120
  scaled_value = value.to_f / (1000 ** scale)
92
-
121
+
93
122
  "%10.3f#{suffix}" % scaled_value
94
123
  end
95
124
  module_function :scale
125
+
126
+ def humanize_duration(duration_ns)
127
+ if duration_ns < 1000
128
+ "%.2f ns" % duration_ns
129
+ elsif duration_ns < 1_000_000
130
+ "%.2f μs" % (duration_ns / 1000)
131
+ elsif duration_ns < 1_000_000_000
132
+ "%.2f ms" % (duration_ns / 1_000_000)
133
+ else
134
+ "%.2f s" % (duration_ns / 1_000_000_000)
135
+ end
136
+ end
137
+ module_function :humanize_duration
96
138
  end
97
139
  end
98
140
 
99
- extend Benchmark::IPS # make ips available as module-level method
141
+ extend Benchmark::IPS # make ips/ips_quick available as module-level method
100
142
 
101
143
  ##
102
144
  # :singleton-method: ips
@@ -4,9 +4,9 @@ module Benchmark
4
4
  # Microseconds per second.
5
5
  MICROSECONDS_PER_SECOND = 1_000_000
6
6
 
7
- # Calculate (arithmetic) mean of given samples.
7
+ # Calculate arithmetic mean of given numbers.
8
8
  # @param [Array] samples Samples to calculate mean.
9
- # @return [Float] Mean of given samples.
9
+ # @return [Float] Mean of given numbers.
10
10
  def self.mean(samples)
11
11
  sum = samples.inject(:+)
12
12
  sum / samples.size
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benchmark-ips
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0
4
+ version: 2.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
@@ -53,6 +53,7 @@ files:
53
53
  - README.md
54
54
  - examples/advanced.rb
55
55
  - examples/hold.rb
56
+ - examples/quick.rb
56
57
  - examples/save.rb
57
58
  - examples/simple.rb
58
59
  - lib/benchmark/compare.rb
@@ -88,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
89
  - !ruby/object:Gem::Version
89
90
  version: '0'
90
91
  requirements: []
91
- rubygems_version: 3.3.7
92
+ rubygems_version: 3.4.20
92
93
  signing_key:
93
94
  specification_version: 4
94
95
  summary: A iterations per second enhancement to Benchmark.