benchmark-swap 0.1.0 → 0.2.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: 300c84291632dc4b0f85e22e83dde631f6baffeaee7d61911adbb62ecceaa288
4
- data.tar.gz: 7c5fd3e887f3af1d60012ae3f62b6be0f04e9f425ebb4e69fdd2cc663ad6f1fd
3
+ metadata.gz: fd86fa1751573aadec3a1c8424f978425a8a0b5d777529b23950d8f93ff104c0
4
+ data.tar.gz: 6a0e1ecfbf5ca6d47edda1f03388084a0112e3c34fbb46e0435725a6c6493da5
5
5
  SHA512:
6
- metadata.gz: 12661ba83db0e3e3f861768b2f90cf096c3acd273df8c9099b7870def338d8f0698cc0cb5f9db37dcbe02a0f6052e365b592513c1bba8d19e1e21ee1aed3cd86
7
- data.tar.gz: 29ee23f65939cf66613bc324c3acb8048c9093b59889d9652208340d2f9acbaad5691865df2ee43474a332cbb13f0cf3b447363c53f88244a327186ca2d33d3c
6
+ metadata.gz: 1fafb8b7eef3fe9b381241ceb939e6a10d43205e8cf92887e971db59087dcface283986294aab3df31d305833359e0136a03d4b52870c91ccce6311cbc2af2dd
7
+ data.tar.gz: '02586225a030da25e823f7ea06efc79fb93baf4ff27c4a7b462bf650d7cc829d3adbd4599fdbea13d8b6a6fbf5b3e269777da92cab4f9c719d7a77b4e05b9b01'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0
3
+ ## Unreleased
4
+
5
+ - Verification no longer crashes the run when a returned value raises from `==` or from `inspect`. It reports a difference instead.
6
+ - An option that benchmark-ips does not know now raises `ArgumentError` instead of being ignored, so a typo no longer runs the benchmark with default settings.
7
+ - The list of swapped methods no longer crashes when a singleton method hangs on an object that raises from `to_s`.
8
+ - The warning shown when the two sides disagree has clearer wording.
9
+
10
+ ## 0.1.0 (2026-09-08)
4
11
 
5
12
  - First release.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Benchmark::Swap
2
2
 
3
+ [![CI](https://github.com/meinac/benchmark-swap/actions/workflows/ci.yml/badge.svg)](https://github.com/meinac/benchmark-swap/actions/workflows/ci.yml)
4
+
3
5
  Compare two implementations of a method with benchmark-ips, without lifting the method out of its call chain into two standalone lambdas. That lift is slow to do and easy to get wrong when the method sits deep inside real code. `Benchmark::Swap` keeps the method where it lives and swaps its body in place.
4
6
 
5
7
  Built for exploring performance changes inside a large Rails app from the Rails console.
@@ -70,6 +72,8 @@ original: 8479556.9 i/s
70
72
 
71
73
  Two "Warming up / Calculating" blocks and one "Comparison:" block are expected. Each side gets its own benchmark-ips run, then the two are compared with the original as the baseline.
72
74
 
75
+ Pass `quiet: true` if you want only the swap list and the comparison.
76
+
73
77
  ## How it works
74
78
 
75
79
  1. **Discovery.** The block runs once under a Ruby `TracePoint` on the `:call` event. It collects every method that was actually called and has a twin with the suffix defined on the same owner. Only calls from the current thread count. A method whose twin lives on a different class or module (for example the original on a parent class, the twin on the child) is skipped. Frozen owners are skipped too.
@@ -87,7 +91,9 @@ The swap itself copies the twin's `UnboundMethod` body onto the original method
87
91
  | `suffix:` | `"_perf"` | Suffix used to find the twin method |
88
92
  | `verify:` | `true` | Compare both sides once before benchmarking |
89
93
  | `output:` | `$stdout` | Where the gem's own report lines go |
90
- | anything else | | Passed to benchmark-ips, for example `time:` and `warmup:` |
94
+ | anything else | | Passed to benchmark-ips config: `warmup`, `time`, `iterations`, `stats`, `confidence`, `quiet`, `suite` |
95
+
96
+ Any other key raises `ArgumentError`, because benchmark-ips would otherwise ignore it and run with the defaults.
91
97
 
92
98
  The call returns a `Runner::Result` struct with `candidates`, `verification`, `original`, and `swapped`. `original` and `swapped` are benchmark-ips `Report` objects. It returns `nil` when no twin was called.
93
99
 
@@ -10,7 +10,9 @@ module Benchmark
10
10
  else
11
11
  "#{owner}##{name}"
12
12
  end
13
- rescue TypeError
13
+ rescue StandardError
14
+ # attached_object is interpolated, so the object decides what happens.
15
+ # Module#to_s on a singleton class needs no cooperation from it.
14
16
  "#{owner}##{name}"
15
17
  end
16
18
 
@@ -15,9 +15,16 @@ module Benchmark
15
15
  SWAPPED_LABEL = "swapped"
16
16
  PREFIX = "benchmark-swap:"
17
17
 
18
+ # Benchmark::IPS::Job#config reads the keys it knows and ignores the
19
+ # rest, which would turn a typo into a benchmark that quietly ran with
20
+ # the defaults.
21
+ IPS_OPTIONS = %i[warmup time iterations stats confidence quiet suite].freeze
22
+
18
23
  Result = Struct.new(:candidates, :verification, :original, :swapped)
19
24
 
20
25
  def initialize(suffix:, verify:, output:, ips_options:)
26
+ reject_unknown(ips_options)
27
+
21
28
  @suffix = suffix
22
29
  @verify = verify
23
30
  @output = output
@@ -40,6 +47,15 @@ module Benchmark
40
47
 
41
48
  private
42
49
 
50
+ def reject_unknown(ips_options)
51
+ unknown = ips_options.keys - IPS_OPTIONS
52
+ return if unknown.empty?
53
+
54
+ raise ArgumentError,
55
+ "unknown option#{"s" if unknown.size > 1}: #{unknown.join(", ")}. " \
56
+ "Known: #{IPS_OPTIONS.join(", ")}"
57
+ end
58
+
43
59
  def nothing_found
44
60
  say "#{PREFIX} no *#{@suffix} twin was called by this block, nothing to compare."
45
61
 
@@ -59,7 +75,7 @@ module Benchmark
59
75
  outcome = Verifier.new.call(swapper, &block)
60
76
  return outcome if outcome.match?
61
77
 
62
- say "#{PREFIX} WARNING both sides returned a different result"
78
+ say "#{PREFIX} WARNING the two sides returned different results"
63
79
  say " #{ORIGINAL_LABEL}: #{outcome.original}"
64
80
  say " #{SWAPPED_LABEL}: #{outcome.swapped}"
65
81
  say ""
@@ -77,10 +93,8 @@ module Benchmark
77
93
  end
78
94
 
79
95
  def ips(label, &block)
80
- options = @ips_options
81
-
82
96
  Benchmark.ips do |job|
83
- job.config(**options) unless options.empty?
97
+ job.config(**@ips_options) unless @ips_options.empty?
84
98
  job.report(label, &block)
85
99
  end
86
100
  end
@@ -21,6 +21,10 @@ module Benchmark
21
21
  return value == other.value unless error || other.error
22
22
 
23
23
  other.error.instance_of?(error.class) && other.error.message == error.message
24
+ rescue StandardError
25
+ # Comparing is best effort, and this class promises a warning rather
26
+ # than a failure. A value that raises from == counts as a difference.
27
+ false
24
28
  end
25
29
 
26
30
  def to_s
@@ -28,6 +32,8 @@ module Benchmark
28
32
 
29
33
  text = value.inspect
30
34
  text.length > MAX_LENGTH ? "#{text[0, MAX_LENGTH]}..." : text
35
+ rescue StandardError
36
+ "(cannot be shown)"
31
37
  end
32
38
  end
33
39
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Benchmark
4
4
  module Swap
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benchmark-swap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mehmet Emin INAC