@@ -7,15 +7,25 @@ namespace BenchmarkDotNet.Portability
7
7
{
8
8
// Implementation is based on article https://medium.com/@meriffa/net-core-concepts-tiered-compilation-10f7da3a29c7
9
9
// documentation https://learn.microsoft.com/en-us/dotnet/core/runtime-config/compilation
10
- // and source https://github.com/dotnet/runtime/blob/71c30b405516b1fe774a1bfdbc43cd804468568f/src/coreclr/vm/eeconfig.cpp
10
+ // and source https://github.com/dotnet/runtime/blob/3fb6fbb3efaa7f2ae5e76bf235615d7f70005201/src/coreclr/vm/eeconfig.cpp
11
+ // https://github.com/dotnet/runtime/blob/3fb6fbb3efaa7f2ae5e76bf235615d7f70005201/src/coreclr/jit/jitconfigvalues.h
11
12
internal static class JitInfo
12
13
{
13
- public const string MinOptsEnv = "JITMinOpts" ;
14
- public const string TieredCompilationEnv = "TieredCompilation" ;
15
- public const string DynamicPGOEnv = "TieredPGO" ;
16
- public const string AggressiveTieringEnv = "TC_AggressiveTiering" ;
17
- public const string CallCountThresholdEnv = "TC_CallCountThreshold" ;
18
- public const string CallCountingDelayMsEnv = "TC_CallCountingDelayMs" ;
14
+ public const string EnvCallCountingDelayMs = "TC_CallCountingDelayMs" ;
15
+
16
+ private const string EnvMinOpts = "JITMinOpts" ;
17
+ private const string EnvTieredCompilation = "TieredCompilation" ;
18
+ private const string EnvQuickJit = "TC_QuickJit" ;
19
+ private const string EnvPGO = "TieredPGO" ;
20
+ private const string EnvCallCountThreshold = "TC_CallCountThreshold" ;
21
+ private const string EnvAggressiveTiering = "TC_AggressiveTiering" ;
22
+ private const string EnvOSR = "TC_OnStackReplacement" ;
23
+
24
+ private const string KnobTieredCompilation = "System.Runtime.TieredCompilation" ;
25
+ private const string KnobQuickJit = "System.Runtime.TieredCompilation.QuickJit" ;
26
+ private const string KnobPGO = "System.Runtime.TieredPGO" ;
27
+ private const string KnobCallCountThreshold = "System.Runtime.TieredCompilation.CallCountThreshold" ;
28
+ private const string KnobCallCountingDelayMs = "System.Runtime.TieredCompilation.CallCountingDelayMs" ;
19
29
20
30
// .Net 5 and older uses COMPlus_ prefix,
21
31
// .Net 6+ uses DOTNET_ prefix, but still supports legacy COMPlus_.
@@ -40,34 +50,71 @@ private static bool IsKnobDisabled(string name)
40
50
private static bool TryParseKnob ( string name , out int value )
41
51
=> int . TryParse ( AppContext . GetData ( name ) as string , out value ) ;
42
52
53
+ private static bool IsEnabled ( string envName , string knobName )
54
+ => IsEnvVarEnabled ( envName ) || IsKnobEnabled ( knobName ) ;
55
+
56
+ private static bool IsDisabled ( string envName , string knobName )
57
+ => IsEnvVarDisabled ( envName ) || IsKnobDisabled ( knobName ) ;
58
+
43
59
/// <summary>
44
60
/// Is tiered JIT enabled?
45
61
/// </summary>
46
62
public static readonly bool IsTiered =
47
63
IsNetCore
48
64
// JITMinOpts disables tiered compilation (all methods are effectively tier0 instead of tier1).
49
- && ! IsEnvVarEnabled ( MinOptsEnv )
65
+ && ! IsEnvVarEnabled ( EnvMinOpts )
50
66
&& ( ( CoreRuntime . TryGetVersion ( out var version ) && version . Major >= 3 )
51
67
// Enabled by default in netcoreapp3.0+, check if it's disabled.
52
- ? ! IsEnvVarDisabled ( TieredCompilationEnv ) && ! IsKnobDisabled ( "System.Runtime.TieredCompilation" )
68
+ ? ! IsDisabled ( EnvTieredCompilation , KnobTieredCompilation )
53
69
// Disabled by default in netcoreapp2.X, check if it's enabled.
54
- : IsEnvVarEnabled ( TieredCompilationEnv ) || IsKnobEnabled ( "System.Runtime.TieredCompilation" ) ) ;
70
+ : IsEnabled ( EnvTieredCompilation , KnobTieredCompilation ) ) ;
55
71
56
72
/// <summary>
57
- /// Is tiered JIT enabled with dynamic profile-guided optimization (tier0 instrumented)?
73
+ /// The maximum numbers of jit tiers that a method may be promoted through. This is the maximum number of jit tiers - 1.
58
74
/// </summary>
59
- public static readonly bool IsDPGO =
60
- IsTiered
61
- // Added experimentally in .Net 6
62
- && Environment . Version . Major >= 6
63
- // Disabled if QuickJit is disabled in .Net 7+.
64
- && ( Environment . Version . Major < 7 || ( ! IsEnvVarDisabled ( "TC_QuickJit" ) && ! IsKnobDisabled ( "System.Runtime.TieredCompilation.QuickJit" ) ) )
65
- && ( Environment . Version . Major >= 8
66
- // Enabled by default in .Net 8, check if it's disabled
67
- ? ! IsEnvVarDisabled ( DynamicPGOEnv ) && ! IsKnobDisabled ( "System.Runtime.TieredPGO" )
68
- // Disabled by default in earlier versions, check if it's enabled.
69
- : IsEnvVarEnabled ( DynamicPGOEnv ) || IsKnobEnabled ( "System.Runtime.TieredPGO" ) ) ;
75
+ public static readonly int MaxTierPromotions = GetMaxTierPromotions ( ) ;
70
76
77
+ private static int GetMaxTierPromotions ( )
78
+ {
79
+ if ( ! IsTiered )
80
+ {
81
+ return 0 ;
82
+ }
83
+ // Tier1
84
+ int maxPromotions = 1 ;
85
+ if ( GetIsDPGO ( ) )
86
+ {
87
+ // Tier0 instrumented
88
+ ++ maxPromotions ;
89
+ }
90
+ if ( GetIsOSR ( ) )
91
+ {
92
+ // On-stack-replacement *shouldn't* interfere with promotion velocity, but there is a bug where OSR may cause a method to be tier0 instrumented twice.
93
+ // https://github.com/dotnet/runtime/issues/117787#issuecomment-3090771091
94
+ ++ maxPromotions ;
95
+ }
96
+ return maxPromotions ;
97
+
98
+ static bool GetIsDPGO ( ) =>
99
+ // Added experimentally in .Net 6.
100
+ Environment . Version . Major >= 6
101
+ // Disabled if QuickJit is disabled in .Net 7+.
102
+ && ( Environment . Version . Major < 7 || ! IsDisabled ( EnvQuickJit , KnobQuickJit ) )
103
+ && ( Environment . Version . Major >= 8
104
+ // Enabled by default in .Net 8, check if it's disabled.
105
+ ? ! IsDisabled ( EnvPGO , KnobPGO )
106
+ // Disabled by default in earlier versions, check if it's enabled.
107
+ : IsEnabled ( EnvPGO , KnobPGO ) ) ;
108
+
109
+ static bool GetIsOSR ( ) =>
110
+ // Added experimentally in .Net 5.
111
+ Environment . Version . Major >= 5
112
+ && ( Environment . Version . Major >= 7
113
+ // Enabled by default in .Net 7, check if it's disabled.
114
+ ? ! IsEnvVarDisabled ( EnvOSR )
115
+ // Disabled by default in earlier versions, check if it's enabled.
116
+ : IsEnvVarEnabled ( EnvOSR ) ) ;
117
+ }
71
118
72
119
/// <summary>
73
120
/// The number of times a method must be called before it will be eligible for the next JIT tier.
@@ -81,16 +128,16 @@ private static int GetTieredCallCountThreshold()
81
128
return 0 ;
82
129
}
83
130
// AggressiveTiering was added in .Net 5.
84
- if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( AggressiveTieringEnv ) )
131
+ if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( EnvAggressiveTiering ) )
85
132
{
86
133
return 1 ;
87
134
}
88
- if ( TryParseEnvVar ( CallCountThresholdEnv , out int callCountThreshold ) )
135
+ if ( TryParseEnvVar ( EnvCallCountThreshold , out int callCountThreshold ) )
89
136
{
90
137
return callCountThreshold ;
91
138
}
92
139
// CallCountThreshold was added as a knob in .Net 8.
93
- if ( Environment . Version . Major >= 8 && TryParseKnob ( "System.Runtime.TieredCompilation.CallCountThreshold" , out callCountThreshold ) )
140
+ if ( Environment . Version . Major >= 8 && TryParseKnob ( KnobCallCountThreshold , out callCountThreshold ) )
94
141
{
95
142
return callCountThreshold ;
96
143
}
@@ -110,16 +157,16 @@ private static TimeSpan GetTieredDelay()
110
157
return TimeSpan . Zero ;
111
158
}
112
159
// AggressiveTiering was added in .Net 5.
113
- if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( AggressiveTieringEnv ) )
160
+ if ( Environment . Version . Major >= 5 && IsEnvVarEnabled ( EnvAggressiveTiering ) )
114
161
{
115
162
return TimeSpan . Zero ;
116
163
}
117
- if ( TryParseEnvVar ( CallCountingDelayMsEnv , out int callCountDelay ) )
164
+ if ( TryParseEnvVar ( EnvCallCountingDelayMs , out int callCountDelay ) )
118
165
{
119
166
return TimeSpan . FromMilliseconds ( callCountDelay ) ;
120
167
}
121
168
// CallCountingDelayMs was added as a knob in .Net 8.
122
- if ( Environment . Version . Major >= 8 && TryParseKnob ( "System.Runtime.TieredCompilation.CallCountingDelayMs" , out callCountDelay ) )
169
+ if ( Environment . Version . Major >= 8 && TryParseKnob ( KnobCallCountingDelayMs , out callCountDelay ) )
123
170
{
124
171
return TimeSpan . FromMilliseconds ( callCountDelay ) ;
125
172
}
0 commit comments