Skip to content

Commit a8073e2

Browse files
committed
Allow comparison against a baseline
This change introduces a new comparison ordering strategy where the initial report serves as the baseline against which the job compares the other reports. This is useful in cases where you're testing out different implementations and want to see whether they are better or worse (and the comparative increase or decrease) in an easy-to-decipher order.
1 parent c705bf0 commit a8073e2

7 files changed

Lines changed: 93 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. This projec
44

55
[semver]: http://semver.org/spec/v2.0.0.html
66

7+
## [Unreleased](https://github.com/michaelherold/benchmark-memory/compare/v0.2.0...main)
8+
9+
### Added
10+
11+
- [#28](https://github.com/michaelherold/benchmark-memory/pull/28): Add the `order: :baseline` comparison method to compare results against a baseline report - [@michaelherold](https://github.com/michaelherold).
12+
713
## [0.2.0](https://github.com/michaelherold/benchmark-memory/compare/v0.1.1...v0.2.0)
814

915
### Added

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ end
113113

114114
When you're looking for a memory leak, this configuration can help you because it compares your reports by the amount of memory that the garbage collector does not collect after the benchmark.
115115

116+
#### Ordering by a baseline
117+
118+
When you're looking to see whether you can improve memory performance by refactoring some code, it can help to visually compare your reports against a baseline. To do so, place your baseline report first and enable the `order: :baseline` option like so:
119+
120+
```ruby
121+
Benchmark.memory do |bench|
122+
bench.report("my baseline") {}
123+
bench.report("another test") {}
124+
125+
bench.compare! order: :baseline
126+
end
127+
```
128+
129+
This will always show the baseline at the top of the comparison and order the alternatives in ascending order against the baseline.
130+
116131
### Hold results between invocations
117132

118133
```ruby

lib/benchmark/memory/job/io_output/comparison_formatter.rb

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,34 @@ def to_s
2828
return '' unless comparison.possible?
2929

3030
output = StringIO.new
31-
best, *rest = comparison.entries
31+
baseline, *rest = comparison.entries
3232
rest = Array(rest)
3333

34-
add_best_summary(best, output)
34+
add_baseline_summary(baseline, output)
3535

3636
rest.each do |entry|
37-
add_comparison(entry, best, output)
37+
add_comparison(entry, baseline, output)
3838
end
3939

4040
output.string
4141
end
4242

4343
private
4444

45-
def add_best_summary(best, output)
46-
output << summary_message("%20s: %10i %s\n", best)
45+
def add_baseline_summary(baseline, output)
46+
output << summary_message('%20s: %10i %s', baseline)
47+
output << ' - baseline' if comparison.baseline?
48+
output << "\n"
4749
end
4850

49-
def add_comparison(entry, best, output)
51+
def add_comparison(entry, baseline, output)
5052
output << summary_message('%20s: %10i %s - ', entry)
51-
output << comparison_between(entry, best)
53+
output << comparison_between(entry, baseline)
5254
output << "\n"
5355
end
5456

55-
def comparison_between(entry, best)
56-
ratio = entry.compared_metric(comparison).to_f / best.compared_metric(comparison)
57+
def comparison_between(entry, baseline)
58+
ratio = entry.compared_metric(comparison).to_f / baseline.compared_metric(comparison)
5759

5860
if ratio.abs > 1
5961
format('%<ratio>.2fx more', ratio: ratio)

lib/benchmark/memory/report/comparator.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,40 @@ class Comparator
1616
# @param spec [Hash<Symbol, Symbol>] The specification given for the {Comparator}
1717
# @return [Comparator]
1818
def self.from_spec(spec)
19+
order =
20+
case spec.delete(:order)
21+
when :baseline then :baseline
22+
else :lowest
23+
end
24+
1925
raise ArgumentError, 'Only send a single metric and value, in the form memory: :allocated' if spec.length > 1
2026

2127
metric, value = *spec.first
2228
metric ||= :memory
2329
value ||= :allocated
2430

25-
new(metric: metric, value: value)
31+
new(metric: metric, order: order, value: value)
2632
end
2733

2834
# Instantiate a new comparator
2935
#
3036
# @param metric [Symbol] (see #metric)
3137
# @param value [Symbol] (see #value)
32-
def initialize(metric: :memory, value: :allocated)
38+
def initialize(metric: :memory, order: :lowest, value: :allocated)
3339
raise ArgumentError, "Invalid metric: #{metric.inspect}" unless METRICS.include? metric
3440
raise ArgumentError, "Invalid value: #{value.inspect}" unless VALUES.include? value
3541

3642
@metric = metric
43+
@order = order
3744
@value = value
3845
end
3946

4047
# @return [Symbol] The metric to compare, one of `:memory`, `:objects`, or `:strings`
4148
attr_reader :metric
4249

50+
# @return [Symbol] The order in which to report results, one of `:lowest` (default) or `:baseline`
51+
attr_reader :order
52+
4353
# @return [Symbol] The value to compare, one of `:allocated` or `:retained`
4454
attr_reader :value
4555

@@ -52,6 +62,13 @@ def ==(other)
5262
metric == other.metric && value == other.value
5363
end
5464

65+
# Checks whether comparing by the baseline
66+
#
67+
# @return [Boolean]
68+
def baseline?
69+
order == :baseline
70+
end
71+
5572
# Converts the {Comparator} to a Proc for passing to a block
5673
#
5774
# @return [Proc]

lib/benchmark/memory/report/comparison.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ class Comparison
1212
# @param entries [Array<Entry>] The entries to compare.
1313
# @param comparator [Comparator] The comparator to use when generating.
1414
def initialize(entries, comparator)
15-
@entries = entries.sort_by(&comparator)
15+
@entries =
16+
if comparator.baseline?
17+
baseline = entries.shift
18+
[baseline, *entries.sort_by(&comparator)]
19+
else
20+
entries.sort_by(&comparator)
21+
end
1622
@comparator = comparator
1723
end
1824

@@ -22,11 +28,15 @@ def initialize(entries, comparator)
2228
# @return [Array<Entry>] The entries to compare.
2329
attr_reader :entries
2430

31+
# @!method baseline?
32+
# @return [Boolean] Whether the comparison will print in baseline order.
2533
# @!method metric
2634
# @return [Symbol] The metric to compare, one of `:memory`, `:objects`, or `:strings`
35+
# @!method order
36+
# @return [Symbol] The order to report results, one of `:lowest`, or `:baseline`
2737
# @!method value
2838
# @return [Symbol] The value to compare, one of `:allocated` or `:retained`
29-
def_delegators :@comparator, :metric, :value
39+
def_delegators :@comparator, :baseline?, :order, :metric, :value
3040

3141
# Check if the comparison is possible
3242
#

spec/benchmark/memory/job/io_output/comparison_formatter_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@
3535

3636
expect(formatter.to_s).to match(/same/)
3737
end
38+
39+
it 'marks the baseline as a baseline when using baseline sorting' do
40+
entries = [create_low_entry, create_high_entry]
41+
comp = Benchmark::Memory::Report::Comparison.new(
42+
entries,
43+
Benchmark::Memory::Report::Comparator.new(order: :baseline)
44+
)
45+
46+
formatter = described_class.new(comp)
47+
48+
expect(formatter.to_s).to match(/- baseline\n.*/)
49+
end
3850
end
3951

4052
def comparison(entries)

spec/benchmark/memory/report/comparison_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313

1414
expect(comparison.entries).to eq([low_entry, high_entry])
1515
end
16+
17+
it 'sorts the baseline first when there is one' do
18+
high_entry = create_high_entry
19+
mid_entry = create_mid_entry
20+
low_entry = create_low_entry
21+
comparator = Benchmark::Memory::Report::Comparator.from_spec({ order: :baseline })
22+
23+
comparison = described_class.new([high_entry, mid_entry, low_entry], comparator)
24+
25+
expect(comparison.entries).to eq([high_entry, low_entry, mid_entry])
26+
end
1627
end
1728

1829
def create_high_entry
@@ -23,6 +34,13 @@ def create_high_entry
2334
end
2435
alias_method :create_entry, :create_high_entry
2536

37+
def create_mid_entry
38+
Benchmark::Memory::Report::Entry.new(
39+
'mid',
40+
create_measurement(5_000, 2_500)
41+
)
42+
end
43+
2644
def create_low_entry
2745
Benchmark::Memory::Report::Entry.new(
2846
'low',

0 commit comments

Comments
 (0)