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 +4 -4
- data/CHANGELOG.md +8 -1
- data/README.md +7 -1
- data/lib/benchmark/swap/candidate.rb +3 -1
- data/lib/benchmark/swap/runner.rb +18 -4
- data/lib/benchmark/swap/verifier.rb +6 -0
- data/lib/benchmark/swap/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd86fa1751573aadec3a1c8424f978425a8a0b5d777529b23950d8f93ff104c0
|
|
4
|
+
data.tar.gz: 6a0e1ecfbf5ca6d47edda1f03388084a0112e3c34fbb46e0435725a6c6493da5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1fafb8b7eef3fe9b381241ceb939e6a10d43205e8cf92887e971db59087dcface283986294aab3df31d305833359e0136a03d4b52870c91ccce6311cbc2af2dd
|
|
7
|
+
data.tar.gz: '02586225a030da25e823f7ea06efc79fb93baf4ff27c4a7b462bf650d7cc829d3adbd4599fdbea13d8b6a6fbf5b3e269777da92cab4f9c719d7a77b4e05b9b01'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
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
|
+
[](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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|