Skip to content

Commit c41913e

Browse files
committed
Updated jit stage to account for OSR.
Don't jit overhead methods if the job is configured to not measure it. Remove extra call counting delay for in-process benchmarks. Set CallCountingDelayMs env var if DisassemblyDiagnoser is not used. Added a test for very long first invocation time.
1 parent 1251e63 commit c41913e

File tree

8 files changed

+159
-80
lines changed

8 files changed

+159
-80
lines changed

src/BenchmarkDotNet/Configs/ImmutableConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ internal ImmutableConfig(
120120

121121
internal bool HasPerfCollectProfiler() => diagnosers.OfType<PerfCollectProfiler>().Any();
122122

123+
internal bool HasDisassemblyDiagnoser() => diagnosers.OfType<DisassemblyDiagnoser>().Any();
124+
123125
public bool HasExtraStatsDiagnoser() => HasMemoryDiagnoser() || HasThreadingDiagnoser() || HasExceptionDiagnoser();
124126

125127
public IDiagnoser? GetCompositeDiagnoser(BenchmarkCase benchmarkCase, RunMode runMode)

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading;
66
using BenchmarkDotNet.Characteristics;
77
using BenchmarkDotNet.Environments;
8+
using BenchmarkDotNet.Helpers;
89
using BenchmarkDotNet.Jobs;
910
using BenchmarkDotNet.Portability;
1011
using BenchmarkDotNet.Reports;
@@ -213,12 +214,9 @@ private ClockSpan Measure(Action<long> action, long invokeCount)
213214
ForceGcCollect();
214215

215216
// #1542
216-
if (JitInfo.BackgroundCompilationDelay > TimeSpan.Zero)
217-
{
218-
// We put the current thread to sleep so tiered jit can kick in, compile its stuff,
219-
// and NOT allocate anything on the background thread when we are measuring allocations.
220-
Thread.Sleep(JitInfo.BackgroundCompilationDelay);
221-
}
217+
// If the jit is tiered, we put the current thread to sleep so it can kick in, compile its stuff,
218+
// and NOT allocate anything on the background thread when we are measuring allocations.
219+
SleepHelper.SleepIfPositive(JitInfo.BackgroundCompilationDelay);
222220

223221
GcStats gcStats;
224222
using (FinalizerBlocker.MaybeStart())

src/BenchmarkDotNet/Engines/EngineJitStage.cs

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading;
4+
using BenchmarkDotNet.Helpers;
45
using BenchmarkDotNet.Jobs;
56
using BenchmarkDotNet.Portability;
67
using BenchmarkDotNet.Reports;
@@ -27,27 +28,35 @@ internal sealed class EngineFirstJitStage : EngineJitStage
2728
// It is not worth spending a long time in jit stage for macro-benchmarks.
2829
private static readonly TimeInterval MaxTieringTime = TimeInterval.FromSeconds(10);
2930

31+
// Jit call counting delay is only for when the app starts up. We don't need to wait for every benchmark if multiple benchmarks are ran in-process.
32+
private static TimeSpan tieredDelay = JitInfo.TieredDelay;
33+
3034
internal bool didStopEarly = false;
3135
internal Measurement lastMeasurement;
3236

3337
private readonly IEnumerator<IterationData> enumerator;
38+
private readonly bool evaluateOverhead;
3439

35-
internal EngineFirstJitStage(EngineParameters parameters) : base(parameters)
40+
internal EngineFirstJitStage(bool evaluateOverhead, EngineParameters parameters) : base(parameters)
3641
{
3742
enumerator = EnumerateIterations();
43+
this.evaluateOverhead = evaluateOverhead;
3844
}
3945

4046
internal override List<Measurement> GetMeasurementList() => new(GetMaxMeasurementCount());
4147

42-
private static int GetMaxMeasurementCount()
48+
private int GetMaxMeasurementCount()
4349
{
44-
int tieredCallCountThreshold = JitInfo.TieredCallCountThreshold;
45-
if (JitInfo.IsDPGO)
50+
if (!JitInfo.IsTiered)
4651
{
47-
tieredCallCountThreshold *= 2;
52+
return 1;
4853
}
49-
// +1 for first jit, x2 for overhead + workload
50-
return (tieredCallCountThreshold + 1) * 2;
54+
int count = JitInfo.MaxTierPromotions* JitInfo.TieredCallCountThreshold + 2;
55+
if (evaluateOverhead)
56+
{
57+
count *= 2;
58+
}
59+
return count;
5160
}
5261

5362
internal override bool GetShouldRunIteration(List<Measurement> measurements, out IterationData iterationData)
@@ -73,8 +82,11 @@ internal override bool GetShouldRunIteration(List<Measurement> measurements, out
7382
private IEnumerator<IterationData> EnumerateIterations()
7483
{
7584
++iterationIndex;
76-
yield return GetDummyIterationData(dummy1Action);
77-
yield return GetOverheadIterationData();
85+
if (evaluateOverhead)
86+
{
87+
yield return GetDummyIterationData(dummy1Action);
88+
yield return GetOverheadIterationData();
89+
}
7890
yield return GetDummyIterationData(dummy2Action);
7991
yield return GetWorkloadIterationData();
8092
yield return GetDummyIterationData(dummy3Action);
@@ -86,12 +98,14 @@ private IEnumerator<IterationData> EnumerateIterations()
8698
}
8799

88100
// Wait enough time for jit call counting to begin.
89-
MaybeSleep(JitInfo.TieredDelay);
101+
SleepHelper.SleepIfPositive(tieredDelay);
102+
// Don't make the next jit stage wait if it's ran in the same process.
103+
tieredDelay = TimeSpan.Zero;
90104

91105
// Attempt to promote methods to tier1, but don't spend too much time in jit stage.
92106
StartedClock startedClock = parameters.TargetJob.ResolveValue(InfrastructureMode.ClockCharacteristic, parameters.Resolver).Start();
93107

94-
int remainingTiers = JitInfo.IsDPGO ? 2 : 1;
108+
int remainingTiers = JitInfo.MaxTierPromotions;
95109
while (remainingTiers > 0)
96110
{
97111
--remainingTiers;
@@ -100,7 +114,10 @@ private IEnumerator<IterationData> EnumerateIterations()
100114
{
101115
--remainingCalls;
102116
++iterationIndex;
103-
yield return GetOverheadIterationData();
117+
if (evaluateOverhead)
118+
{
119+
yield return GetOverheadIterationData();
120+
}
104121
yield return GetWorkloadIterationData();
105122

106123
if ((remainingTiers + remainingCalls) > 0
@@ -111,13 +128,16 @@ private IEnumerator<IterationData> EnumerateIterations()
111128
}
112129
}
113130

114-
MaybeSleep(JitInfo.BackgroundCompilationDelay);
131+
SleepHelper.SleepIfPositive(JitInfo.BackgroundCompilationDelay);
115132
}
116133

117-
// Empirical evidence shows that the first call after the method is tiered up takes longer,
134+
// Empirical evidence shows that the first call after the method is tiered up may take longer,
118135
// so we run an extra iteration to ensure the next stage gets a stable measurement.
119136
++iterationIndex;
120-
yield return GetOverheadIterationData();
137+
if (evaluateOverhead)
138+
{
139+
yield return GetOverheadIterationData();
140+
}
121141
yield return GetWorkloadIterationData();
122142
}
123143

@@ -126,32 +146,22 @@ private IterationData GetOverheadIterationData()
126146

127147
private IterationData GetWorkloadIterationData()
128148
=> new(IterationMode.Workload, IterationStage.Jitting, iterationIndex, 1, 1, parameters.IterationSetupAction, parameters.IterationCleanupAction, parameters.WorkloadActionNoUnroll);
129-
130-
private static void MaybeSleep(TimeSpan timeSpan)
131-
{
132-
if (timeSpan > TimeSpan.Zero)
133-
{
134-
Thread.Sleep(timeSpan);
135-
}
136-
}
137149
}
138150

139-
internal sealed class EngineSecondJitStage(int unrollFactor, EngineParameters parameters) : EngineJitStage(parameters)
151+
internal sealed class EngineSecondJitStage : EngineJitStage
140152
{
141-
private readonly int unrollFactor = unrollFactor;
153+
private readonly int unrollFactor;
154+
private readonly bool evaluateOverhead;
142155

143-
internal override List<Measurement> GetMeasurementList() => new(GetMaxCallCount());
144-
145-
private static int GetMaxCallCount()
156+
public EngineSecondJitStage(int unrollFactor, bool evaluateOverhead, EngineParameters parameters) : base(parameters)
146157
{
147-
int tieredCallCountThreshold = JitInfo.TieredCallCountThreshold;
148-
if (JitInfo.IsDPGO)
149-
{
150-
tieredCallCountThreshold *= 2;
151-
}
152-
return tieredCallCountThreshold + 1;
158+
this.unrollFactor = unrollFactor;
159+
this.evaluateOverhead = evaluateOverhead;
160+
iterationIndex = evaluateOverhead ? 0 : 2;
153161
}
154162

163+
internal override List<Measurement> GetMeasurementList() => new(evaluateOverhead ? 5 : 3);
164+
155165
// The benchmark method has already been jitted via *NoUnroll, we only need to jit the *Unroll methods here, which aren't tiered.
156166
internal override bool GetShouldRunIteration(List<Measurement> measurements, out IterationData iterationData)
157167
{

src/BenchmarkDotNet/Engines/EngineStage.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal static IEnumerable<EngineStage> EnumerateStages(EngineParameters parame
3333
int minInvokeCount = parameters.TargetJob.ResolveValue(AccuracyMode.MinInvokeCountCharacteristic, parameters.Resolver);
3434

3535
// AOT technically doesn't have a JIT, but we run jit stage regardless because of static constructors. #2004
36-
var jitStage = new EngineFirstJitStage(parameters);
36+
var jitStage = new EngineFirstJitStage(evaluateOverhead, parameters);
3737
yield return jitStage;
3838

3939
bool hasUnrollFactor = parameters.TargetJob.HasValue(RunMode.UnrollFactorCharacteristic);
@@ -62,7 +62,7 @@ internal static IEnumerable<EngineStage> EnumerateStages(EngineParameters parame
6262
// TODO: This stage can be removed after we refactor the engine/codegen to pass the clock into the delegates.
6363
if (!RuntimeInformation.IsAot && unrollFactor != 1)
6464
{
65-
yield return new EngineSecondJitStage(unrollFactor, parameters);
65+
yield return new EngineSecondJitStage(unrollFactor, evaluateOverhead, parameters);
6666
}
6767

6868
if (!skipPilotStage)

src/BenchmarkDotNet/Extensions/ProcessExtensions.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,11 @@ internal static void SetEnvironmentVariables(this ProcessStartInfo start, Benchm
150150
// disable ReSharper's Dynamic Program Analysis (see https://github.com/dotnet/BenchmarkDotNet/issues/1871 for details)
151151
start.EnvironmentVariables["JETBRAINS_DPA_AGENT_ENABLE"] = "0";
152152

153-
// TODO: set CallCountingDelayMs without breaking DisassemblyDiagnoser. https://github.com/dotnet/runtime/issues/117339
154-
//SetClrEnvironmentVariables(start, JitInfo.CallCountingDelayMsEnv, "0");
153+
// CallCountingDelayMs=0 breaks DisassemblyDiagnoser, so we only set it if the job doesn't need disassembly. https://github.com/dotnet/runtime/issues/117339
154+
if (!benchmarkCase.Config.HasDisassemblyDiagnoser())
155+
{
156+
SetClrEnvironmentVariables(start, JitInfo.EnvCallCountingDelayMs, "0");
157+
}
155158

156159
if (!benchmarkCase.Job.HasValue(EnvironmentMode.EnvironmentVariablesCharacteristic))
157160
return;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace BenchmarkDotNet.Helpers
5+
{
6+
internal static class SleepHelper
7+
{
8+
public static void SleepIfPositive(TimeSpan timeSpan)
9+
{
10+
if (timeSpan > TimeSpan.Zero)
11+
{
12+
Thread.Sleep(timeSpan);
13+
}
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)