Skip to content

Commit aee34d3

Browse files
committed
Basic reference_job feature
Basis is from #281 which we let lie for too long, sorry. Also too much had changed in the mean time (`RelativeStatistics`) so reopening it didn't make a ton of sesnse. Fixes #179
1 parent a62708c commit aee34d3

File tree

3 files changed

+127
-12
lines changed

3 files changed

+127
-12
lines changed

lib/benchee/configuration.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ defmodule Benchee.Configuration do
3939
after_scenario: nil,
4040
measure_function_call_overhead: false,
4141
title: nil,
42-
profile_after: false
42+
profile_after: false,
43+
reference_job: nil
4344

4445
@typedoc """
4546
The configuration supplied by the user as either a map or a keyword list
@@ -133,6 +134,7 @@ defmodule Benchee.Configuration do
133134
[`:cprof`](https://hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html),
134135
[`:eprof`](https://hexdocs.pm/mix/Mix.Tasks.Profile.Eprof.html) and
135136
[`:fprof`](https://hexdocs.pm/mix/Mix.Tasks.Profile.Fprof.html).
137+
* `:reference_job` - Set which function in the benchmark all others will be compared against.
136138
"""
137139
@type user_configuration :: map | keyword
138140

@@ -163,7 +165,8 @@ defmodule Benchee.Configuration do
163165
after_scenario: Hooks.hook_function() | nil,
164166
measure_function_call_overhead: boolean,
165167
title: String.t() | nil,
166-
profile_after: boolean | atom | {atom, keyword}
168+
profile_after: boolean | atom | {atom, keyword},
169+
reference_job: String.t() | nil
167170
}
168171

169172
@time_keys [:time, :warmup, :memory_time, :reduction_time]

lib/benchee/relative_statistics.ex

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule Benchee.RelativeStatistics do
99
has to happen before they are loaded to avoid recalculating their statistics.
1010
"""
1111

12-
alias Benchee.{Scenario, Statistics, Suite}
12+
alias Benchee.{Configuration, Scenario, Statistics, Suite}
1313

1414
@doc """
1515
Calculate the statistics of scenarios relative to each other and sorts scenarios.
@@ -22,17 +22,17 @@ defmodule Benchee.RelativeStatistics do
2222
"""
2323
@spec relative_statistics(Suite.t()) :: Suite.t()
2424
def relative_statistics(suite) do
25-
%Suite{suite | scenarios: calculate_relative_statistics(suite.scenarios)}
25+
%Suite{suite | scenarios: calculate_relative_statistics(suite.scenarios, suite.configuration)}
2626
end
2727

28-
defp calculate_relative_statistics([]), do: []
28+
defp calculate_relative_statistics([], _config), do: []
2929

30-
defp calculate_relative_statistics(scenarios) do
30+
defp calculate_relative_statistics(scenarios, config) do
3131
scenarios
3232
|> scenarios_by_input()
3333
|> Enum.flat_map(fn scenarios_with_same_input ->
3434
sorted_scenarios = sort(scenarios_with_same_input)
35-
{reference, others} = split_reference_scenario(sorted_scenarios)
35+
{reference, others} = split_reference_scenario(sorted_scenarios, config)
3636
others_with_relative = statistics_relative_to(others, reference)
3737
[reference | others_with_relative]
3838
end)
@@ -59,9 +59,25 @@ defmodule Benchee.RelativeStatistics do
5959
end)
6060
end
6161

62-
# right now we take the first scenario as we sorted them and it is the fastest,
63-
# whenever we implement #179 though this becomes more involved
64-
defp split_reference_scenario(scenarios) do
62+
defp split_reference_scenario(scenarios, config)
63+
64+
defp split_reference_scenario(scenarios, %Configuration{reference_job: reference_job})
65+
when is_binary(reference_job) do
66+
split_scenarios =
67+
Enum.split_with(scenarios, fn scenario -> scenario.name == reference_job end)
68+
69+
case split_scenarios do
70+
{[reference_scenario], others} -> {reference_scenario, others}
71+
_reference_not_found -> fastest_as_reference(scenarios)
72+
end
73+
end
74+
75+
# no reference_job configured? Just take the first one, as this is post sort it'll be the fastet
76+
defp split_reference_scenario(scenarios, _config) do
77+
fastest_as_reference(scenarios)
78+
end
79+
80+
defp fastest_as_reference(scenarios) do
6581
[reference | others] = scenarios
6682
{reference, others}
6783
end

test/benchee/relative_statistics_test.exs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Benchee.RelativeStatistcsTest do
22
use ExUnit.Case, async: true
33

4-
alias Benchee.{CollectionData, Scenario, Statistics, Suite}
4+
alias Benchee.{CollectionData, Configuration, Scenario, Statistics, Suite}
55
import Benchee.RelativeStatistics
66

77
describe "computing relative statistics" do
@@ -43,7 +43,7 @@ defmodule Benchee.RelativeStatistcsTest do
4343
assert stats.relative_less == nil
4444
end
4545

46-
test "handles the fastest value being zerio alright" do
46+
test "handles the fastest value being zero alright" do
4747
suite = %Suite{
4848
scenarios: [
4949
scenario_with_average(0.0),
@@ -98,6 +98,55 @@ defmodule Benchee.RelativeStatistcsTest do
9898
assert stats2.relative_less == 1.0
9999
assert stats2.absolute_difference == 0.0
100100
end
101+
102+
test "setting the reference job to be the slowest" do
103+
suite = %Suite{
104+
scenarios: [
105+
named_scenario_with_average("A", 394.0, nil),
106+
named_scenario_with_average("B", 14.0, nil)
107+
],
108+
configuration: %Configuration{reference_job: "A"}
109+
}
110+
111+
suite = relative_statistics(suite)
112+
# reference scenario always comes out on top even if slowest
113+
assert [a_stats, b_stats] = stats_from(suite)
114+
115+
assert a_stats.relative_more == nil
116+
assert a_stats.relative_less == nil
117+
assert a_stats.absolute_difference == nil
118+
119+
assert b_stats.absolute_difference == -380.0
120+
assert_in_delta b_stats.relative_more, 0.0355, 0.001
121+
assert_in_delta b_stats.relative_less, 28.14, 0.1
122+
end
123+
124+
test "sandwich reference job" do
125+
suite = %Suite{
126+
scenarios: [
127+
named_scenario_with_average("A", 1.0, nil),
128+
named_scenario_with_average("B", 2.0, nil),
129+
named_scenario_with_average("C", 3.0, nil)
130+
],
131+
configuration: %Configuration{reference_job: "B"}
132+
}
133+
134+
suite = relative_statistics(suite)
135+
# reference scenario always comes out on top even if slowest
136+
assert [b_stats, a_stats, c_stats] = stats_from(suite)
137+
138+
assert b_stats.relative_more == nil
139+
assert b_stats.relative_less == nil
140+
assert b_stats.absolute_difference == nil
141+
142+
assert a_stats.absolute_difference == -1.0
143+
assert_in_delta a_stats.relative_more, 0.5, 0.001
144+
assert_in_delta a_stats.relative_less, 2.0, 0.01
145+
146+
assert c_stats.absolute_difference == 1.0
147+
assert_in_delta c_stats.relative_more, 1.5, 0.001
148+
assert_in_delta c_stats.relative_less, 0.66, 0.01
149+
end
101150
end
102151

103152
describe "sorting behaviour" do
@@ -138,6 +187,24 @@ defmodule Benchee.RelativeStatistcsTest do
138187

139188
assert Enum.map(sorted, fn scenario -> scenario.name end) == ["1", "2", "3"]
140189
end
190+
191+
test "given a configured reference_job returns that as the first regardless of performance" do
192+
fourth = named_scenario_with_average("4", 400.1, nil)
193+
second = named_scenario_with_average("2", 200.0, nil)
194+
third = named_scenario_with_average("3", 400.0, nil)
195+
first = named_scenario_with_average("1", 100.0, nil)
196+
reference = named_scenario_with_average("Ref", 500.0, nil)
197+
198+
scenarios = [fourth, second, third, first, reference]
199+
200+
sorted =
201+
relative_statistics(%Suite{
202+
scenarios: scenarios,
203+
configuration: %Configuration{reference_job: "Ref"}
204+
}).scenarios
205+
206+
assert Enum.map(sorted, fn scenario -> scenario.name end) == ["Ref", "1", "2", "3", "4"]
207+
end
141208
end
142209

143210
describe "dealing correctly with different inputs" do
@@ -187,6 +254,35 @@ defmodule Benchee.RelativeStatistcsTest do
187254
]
188255
end
189256

257+
test "calculate the correct relatives based on input names even given a reference_job" do
258+
suite = %Suite{
259+
scenarios: [
260+
named_input_scenario_with_average("A", "1", 100.0),
261+
named_input_scenario_with_average("B", "1", 200.0),
262+
named_input_scenario_with_average("A", "10", 1_000.0),
263+
named_input_scenario_with_average("B", "10", 10_000.0)
264+
],
265+
configuration: %Configuration{reference_job: "B"}
266+
}
267+
268+
suite = relative_statistics(suite)
269+
270+
stats =
271+
suite.scenarios
272+
|> Enum.map(
273+
&{&1.name, &1.input_name, &1.run_time_data.statistics.absolute_difference,
274+
&1.run_time_data.statistics.relative_more}
275+
)
276+
|> Enum.sort()
277+
278+
assert stats == [
279+
{"A", "1", -100.0, 0.5},
280+
{"A", "10", -9_000, 0.1},
281+
{"B", "1", nil, nil},
282+
{"B", "10", nil, nil}
283+
]
284+
end
285+
190286
test "calculate the correct relatives based on different ordering" do
191287
suite = %Suite{
192288
scenarios: [

0 commit comments

Comments
 (0)