Skip to content

Commit 9560515

Browse files
Add Allocation Ratio column (#1859)
Co-authored-by: Adam Sitnik <[email protected]>
1 parent f00f7c7 commit 9560515

File tree

5 files changed

+119
-15
lines changed

5 files changed

+119
-15
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using BenchmarkDotNet.Diagnosers;
5+
using BenchmarkDotNet.Mathematics;
6+
using BenchmarkDotNet.Reports;
7+
using BenchmarkDotNet.Running;
8+
using JetBrains.Annotations;
9+
10+
namespace BenchmarkDotNet.Columns
11+
{
12+
public class BaselineAllocationRatioColumn : BaselineCustomColumn
13+
{
14+
public override string Id => nameof(BaselineAllocationRatioColumn);
15+
public override string ColumnName => "Alloc Ratio";
16+
17+
public override string GetValue(Summary summary, BenchmarkCase benchmarkCase, Statistics baseline, IReadOnlyDictionary<string, Metric> baselineMetrics,
18+
Statistics current, IReadOnlyDictionary<string, Metric> currentMetrics, bool isBaseline)
19+
{
20+
double? ratio = GetAllocationRatio(currentMetrics, baselineMetrics);
21+
double? invertedRatio = GetAllocationRatio(baselineMetrics, currentMetrics);
22+
23+
if (ratio == null)
24+
return "NA";
25+
26+
var cultureInfo = summary.GetCultureInfo();
27+
var ratioStyle = summary?.Style?.RatioStyle ?? RatioStyle.Value;
28+
29+
bool advancedPrecision = IsNonBaselinesPrecise(summary, baselineMetrics, benchmarkCase);
30+
switch (ratioStyle)
31+
{
32+
case RatioStyle.Value:
33+
return ratio.Value.ToString(advancedPrecision ? "N3" : "N2", cultureInfo);
34+
case RatioStyle.Percentage:
35+
return isBaseline
36+
? ""
37+
: ratio.Value >= 1.0
38+
? "+" + ((ratio.Value - 1.0) * 100).ToString(advancedPrecision ? "N1" : "N0", cultureInfo) + "%"
39+
: "-" + ((1.0 - ratio.Value) * 100).ToString(advancedPrecision ? "N1" : "N0", cultureInfo) + "%";
40+
case RatioStyle.Trend:
41+
return isBaseline
42+
? ""
43+
: ratio.Value >= 1.0
44+
? ratio.Value.ToString(advancedPrecision ? "N3" : "N2", cultureInfo) + "x more"
45+
: invertedRatio == null
46+
? "NA"
47+
: invertedRatio.Value.ToString(advancedPrecision ? "N3" : "N2", cultureInfo) + "x less";
48+
default:
49+
throw new ArgumentOutOfRangeException(nameof(summary), ratioStyle, "RatioStyle is not supported");
50+
}
51+
}
52+
53+
private static bool IsNonBaselinesPrecise(Summary summary, IReadOnlyDictionary<string, Metric> baselineMetric, BenchmarkCase benchmarkCase)
54+
{
55+
string logicalGroupKey = summary.GetLogicalGroupKey(benchmarkCase);
56+
var nonBaselines = summary.GetNonBaselines(logicalGroupKey);
57+
return nonBaselines.Any(c => GetAllocationRatio(summary[c].Metrics, baselineMetric) is > 0 and < 0.01);
58+
}
59+
60+
private static double? GetAllocationRatio(
61+
[CanBeNull] IReadOnlyDictionary<string, Metric> current,
62+
[CanBeNull] IReadOnlyDictionary<string, Metric> baseline)
63+
{
64+
double? currentBytes = GetAllocatedBytes(current);
65+
double? baselineBytes = GetAllocatedBytes(baseline);
66+
67+
if (currentBytes == null || baselineBytes == null)
68+
return null;
69+
70+
if (baselineBytes == 0)
71+
return null;
72+
73+
return currentBytes / baselineBytes;
74+
}
75+
76+
private static double? GetAllocatedBytes([CanBeNull] IReadOnlyDictionary<string, Metric> metrics)
77+
{
78+
var metric = metrics?.Values.FirstOrDefault(m => m.Descriptor is AllocatedMemoryMetricDescriptor);
79+
return metric?.Value;
80+
}
81+
82+
public override ColumnCategory Category => ColumnCategory.Metric; //it should be displayed after Allocated column
83+
public override int PriorityInCategory => AllocatedMemoryMetricDescriptor.Instance.PriorityInCategory + 1;
84+
public override bool IsNumeric => true;
85+
public override UnitType UnitType => UnitType.Dimensionless;
86+
public override string Legend => "Allocated memory ratio distribution ([Current]/[Baseline])";
87+
}
88+
}

src/BenchmarkDotNet/Columns/BaselineCustomColumn.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public abstract string GetValue(Summary summary, BenchmarkCase benchmarkCase, St
3434

3535
public bool IsAvailable(Summary summary) => summary.HasBaselines();
3636
public bool AlwaysShow => true;
37-
public ColumnCategory Category => ColumnCategory.Baseline;
37+
public virtual ColumnCategory Category => ColumnCategory.Baseline;
3838
public abstract int PriorityInCategory { get; }
3939
public abstract bool IsNumeric { get; }
4040
public abstract UnitType UnitType { get; }

src/BenchmarkDotNet/Columns/DefaultColumnProvider.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,23 @@ public IEnumerable<IColumn> GetColumns(Summary summary)
6363
bool hide = stdDevColumnValues.All(value => value == "0.00" || value == "0.01");
6464
if (!hide)
6565
yield return BaselineRatioColumn.RatioStdDev;
66+
67+
if (HasMemoryDiagnoser(summary))
68+
{
69+
yield return new BaselineAllocationRatioColumn();
70+
}
6671
}
6772
}
6873

6974
private static bool NeedToShow(Summary summary, Func<Statistics, bool> check)
7075
{
7176
return summary.Reports != null && summary.Reports.Any(r => r.ResultStatistics != null && check(r.ResultStatistics));
7277
}
78+
79+
private static bool HasMemoryDiagnoser(Summary summary)
80+
{
81+
return summary.BenchmarksCases.Any(c => c.Config.HasMemoryDiagnoser());
82+
}
7383
}
7484

7585
private class ParamsColumnProvider : IColumnProvider
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using BenchmarkDotNet.Columns;
3+
using BenchmarkDotNet.Reports;
4+
5+
namespace BenchmarkDotNet.Diagnosers
6+
{
7+
internal class AllocatedMemoryMetricDescriptor : IMetricDescriptor
8+
{
9+
internal static readonly IMetricDescriptor Instance = new AllocatedMemoryMetricDescriptor();
10+
11+
public string Id => "Allocated Memory";
12+
public string DisplayName => "Allocated";
13+
public string Legend => "Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)";
14+
public string NumberFormat => "0.##";
15+
public UnitType UnitType => UnitType.Size;
16+
public string Unit => SizeUnit.B.Name;
17+
public bool TheGreaterTheBetter => false;
18+
public int PriorityInCategory => GC.MaxGeneration + 1;
19+
}
20+
}

src/BenchmarkDotNet/Diagnosers/MemoryDiagnoser.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,6 @@ public IEnumerable<Metric> ProcessResults(DiagnoserResults diagnoserResults)
4545
yield return new Metric(AllocatedMemoryMetricDescriptor.Instance, diagnoserResults.GcStats.GetBytesAllocatedPerOperation(diagnoserResults.BenchmarkCase));
4646
}
4747

48-
private class AllocatedMemoryMetricDescriptor : IMetricDescriptor
49-
{
50-
internal static readonly IMetricDescriptor Instance = new AllocatedMemoryMetricDescriptor();
51-
52-
public string Id => "Allocated Memory";
53-
public string DisplayName => "Allocated";
54-
public string Legend => "Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)";
55-
public string NumberFormat => "0.##";
56-
public UnitType UnitType => UnitType.Size;
57-
public string Unit => SizeUnit.B.Name;
58-
public bool TheGreaterTheBetter => false;
59-
public int PriorityInCategory => GC.MaxGeneration + 1;
60-
}
61-
6248
private class GarbageCollectionsMetricDescriptor : IMetricDescriptor
6349
{
6450
internal static readonly IMetricDescriptor Gen0 = new GarbageCollectionsMetricDescriptor(0);

0 commit comments

Comments
 (0)