-
-
Notifications
You must be signed in to change notification settings - Fork 990
Issue #1024: Calculate baseline by the fastest benchmark #2171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the following benchmark will be executed instead of the failed validation:
[AutomaticBaseline(AutomaticBaselineMode.Fastest)]
public class Benchmarks
{
[Benchmark(Baseline = true)] public void M1() { }
[Benchmark] public void M2() { Thread.Sleep(1);}
}
You can set a baseline for a Job
, check it out too:
[AutomaticBaseline(AutomaticBaselineMode.Fastest)]
[SimpleJob(RuntimeMoniker.Net60, baseline: true)]
public class Benchmarks { ... }
For maintainers:
maybe we should add Slowest
for consistency?
maybe we should add "Fastest"
/"Slowest"
for some other columns, like Allocation
?
var fastestReport = reports.First(); | ||
foreach (var report in reports.Skip(1)) | ||
{ | ||
if (report.ResultStatistics.Mean < fastestReport.ResultStatistics.Mean) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ResultStatistics
can be null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of the failed validation:
I actually thought about it but decided to keep a new feature overriding more local configurations. Added a validation.
maybe we should add "Fastest"/"Slowest" for some other columns, like Allocation?
Could make sense but not to me. Ratio column is enough for me, the fastest is just a baseline (ratio == 1)
ResultStatistics can be null.
Fixed
Thanks for your review Yegor 👍
Reports = BenchmarksCases.Select(b => ReportMap[b]).ToImmutableArray(); // we use sorted collection to re-create reports list | ||
BaseliningStrategy = BaseliningStrategy.Create(BenchmarksCases); | ||
Style = GetConfiguredSummaryStyleOrDefaultOne(BenchmarksCases).WithCultureInfo(cultureInfo); | ||
Table = GetTable(Style); | ||
AllRuntimes = BuildAllRuntimes(HostEnvironmentInfo, Reports); | ||
} | ||
|
||
private static BenchmarkCase GetFastestBenchmarkCase(ImmutableArray<BenchmarkReport> reports) | ||
{ | ||
if (reports.Any() && reports.All(r => r.BenchmarkCase.Config.AutomaticBaselineMode == AutomaticBaselineMode.Fastest)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reports.All(r => r.BenchmarkCase.Config.AutomaticBaselineMode == AutomaticBaselineMode.Fastest))
Is it correctly works when 2 benchmark classes have different AutomaticBaselineMode
?
[AutomaticBaseline(AutomaticBaselineMode.Fastest)]
public class C1 { }
[AutomaticBaseline(AutomaticBaselineMode.None)]
public class C2 { }
To join summaries, use:
--join
(for cmd)BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).RunAllJoined()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea itself works because the benchmarks are already grouped by the type so that Summary contains the cases of either C1 or C2 only. However, this particular example doesn't print the ratio for all the methods. I guess BDN trims columns that don't exist in all results (C1's benchmarks contain ration but C2's ones don't). If you set AutomaticBaselineMode.Fastest
for C2, it prints correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ManualConfig.Union(...)
resets the AutomaticBaselineMode
:
config = ManualConfig.Union(config, configFromAttribute.Config) // C1, set the value to "Fastest"
config = ManualConfig.Union(config, configFromAttribute.Config) // C2, set the value to "None" (even if the `[AutomaticBaseline(None)]` is not used)
The problem is that the final config has only one value of AutomaticBaselineMode
, but it should have a different value for each benchmark class to allow:
[AutomaticBaseline(AutomaticBaselineMode.Fastest)] public class B1 { }
public class B2 { } //implicit AutomaticBaselineMode.None
[AutomaticBaseline(AutomaticBaselineMode.Slowest)] public class B3 { }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is not like that, the configs are different. I see that this inferredBaselineBenchmarkCase is calculated properly. There is probably a need to add a column or something like that, I will try to identify the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is like that :)
Apply it first:
if (reports.Any() && reports.All(r => r.BenchmarkCase.Config.AutomaticBaselineMode == AutomaticBaselineMode.Fastest)) | |
if (reports.Any() && reports.Any(r => r.BenchmarkCase.Config.AutomaticBaselineMode == AutomaticBaselineMode.Fastest)) |
Sample
[DryJob]
// [AutomaticBaseline(AutomaticBaselineMode.Fastest)]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] // it applies to all summaries, we may not duplicate it
[BenchmarkCategory("A")] // without this, the table is not split by classes
public class MyClass1
{
[Benchmark] public void MyMethod1() => Thread.Sleep(100);
[Benchmark] public void MyMethod2() => Thread.Sleep(200);
}
[DryJob]
// [AutomaticBaseline(AutomaticBaselineMode.Fastest)]
[BenchmarkCategory("B")]
public class MyClass2
{
[Benchmark] public void MyMethod1() => Thread.Sleep(400);
[Benchmark] public void MyMethod2() => Thread.Sleep(200);
}
If we uncomment AutomaticBaseline
for MyClass1
only:
If we uncomment AutomaticBaseline
for MyClass2
only (this is the correct table):
Debug these lines to see how IConfig.AutomaticBaselineMode
changes:
BenchmarkDotNet/src/BenchmarkDotNet/Running/BenchmarkConverter.cs
Lines 106 to 107 in 6bb61ce
foreach (var configFromAttribute in methodAttributes) | |
config = ManualConfig.Union(config, configFromAttribute.Config); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, you are right. I didn't notice that Summary is built the third time (where you have all 4 methods). I was checking first two Summary instances (first two benchmarks, then second two). Thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is what I suggest we do d20c117
- We skip all defaut (
None
) values to build configs. - We take the latest non-default value to determine whether we need to override baseline functionality or we don't.
I am not very familiar with this part of BDN and if possible I would prefer @AndreyAkinshin to review it. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this feature, it looks cool! Thanks for the contribution!
My primary concern is about the tests. Thread.Sleep-based integration tests are too fragile and too slow, we should avoid them whenever it's possible. We need fast regular unit tests to cover this feature. In order to do it, we can use MockFactory
to create an artifical summary based on the given type and check the column values manually. It would be a fast, reliable and isolated testing approach.
I tried to fix all the notes we have here, please let me know if there is anything else to deal with @YegorStepanov @AndreyAkinshin @adamsitnik |
Cons of the current implementation:We cannot extend the enum, because attribute actually set a boolean flag in config. I like the current attribute API but it will need to rename the attribute, if we drop the enum. I'm thinking of a more powerful solution:Not for all users
API:[AutomaticBaseline(string columnName, Strategy strategy = Maximum)]
enum Strategy { Minimum, Maximum } // AutomaticBaselineStrategy or AutomaticStrategy or BaselineStrategy? how to use:[AutomaticBaseline("P90")]
[AutomaticBaseline(Column.P90))] We can compare values from the Questions:Does it make sense to add support for [assembly: [AutomaticBaseline("Mean")]]
public Benchmarks
{
// currently, it would be an error: "Do not use both AutomaticBaseline and [Benchmark(Baseline = true)]"
[Benchmark(Baseline = true)] public void B1() { }
[Benchmark] public void B1() => Thread.Sleep(100);
} How can this be resolved:
If Serg046 does not have so much time, then I can do it within a few months. |
Co-authored-by: Yegor Stepanov <yegor.stepanov@outlook.com>
The idea looks very interesting to me. If mainterners like it too, I am also open to make it happen. The only thought I have is that it looks as a separate feature. I think this should be working for both automatic and explicit/manual baseline modes, while this PR is focused on a new auto one only. We can create a new issue and discuss a feature design there. However, if we want to do something here, I am still ready for that! |
Are both solutions really needed? They look very similar: [AutomaticBaseline(AutomaticBaselineMode.Fastest)]
[AutomaticBaseline("Mean")] // how to name it? AutomaticBaselineMode.FastestAs I wrote above, it is not clear what the Why is it compared by the If we change it to the |
No, I meant it would be great to suport it for manual/explicit mode as well as for the new AutomaticBaseline attribute. Something like that: public Benchmarks
{
// Existing functionality with a new feature
[Benchmark(Baseline = true, BaselineMetric = "Mean")] public void B1() { }
}
Just my choice during development. I am fine to change this default to something else, e.g.
Exactly |
The |
Fixes #1024
The idea is inside
Summary.cs