Skip to content

Commit 9e9f7b2

Browse files
committed
Use contention detection for self-tuning
1 parent 7cf910b commit 9e9f7b2

File tree

3 files changed

+439
-939
lines changed

3 files changed

+439
-939
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Lock.NativeAot.cs

Lines changed: 44 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,27 @@
33

44
using System.Diagnostics;
55
using System.Diagnostics.Tracing;
6+
using System.Runtime;
67
using System.Runtime.CompilerServices;
78

89
namespace System.Threading
910
{
1011
public sealed partial class Lock
1112
{
12-
private const short SpinCountNotInitialized = short.MinValue;
13-
1413
// NOTE: Lock must not have a static (class) constructor, as Lock itself is used to synchronize
1514
// class construction. If Lock has its own class constructor, this can lead to infinite recursion.
1615
// All static data in Lock must be lazy-initialized.
1716
private static int s_staticsInitializationStage;
18-
private static bool s_isSingleProcessor;
17+
private static int s_processorCount;
1918
private static short s_maxSpinCount;
2019
private static short s_minSpinCount;
2120

22-
/// <summary>
23-
/// Initializes a new instance of the <see cref="Lock"/> class.
24-
/// </summary>
25-
public Lock() => _spinCount = SpinCountNotInitialized;
26-
2721
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2822
internal bool TryEnterOneShot(int currentManagedThreadId)
2923
{
3024
Debug.Assert(currentManagedThreadId != 0);
3125

32-
if (State.TryLock(this))
26+
if (this.TryLock())
3327
{
3428
Debug.Assert(_owningThreadId == 0);
3529
Debug.Assert(_recursionCount == 0);
@@ -54,20 +48,20 @@ internal void Exit(int currentManagedThreadId)
5448
}
5549

5650
[MethodImpl(MethodImplOptions.AggressiveInlining)]
57-
private ThreadId TryEnterSlow(int timeoutMs, ThreadId currentThreadId) =>
51+
private bool TryEnterSlow(int timeoutMs, ThreadId currentThreadId) =>
5852
TryEnterSlow(timeoutMs, currentThreadId, this);
5953

6054
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6155
internal bool TryEnterSlow(int timeoutMs, int currentManagedThreadId, object associatedObject) =>
62-
TryEnterSlow(timeoutMs, new ThreadId((uint)currentManagedThreadId), associatedObject).IsInitialized;
56+
TryEnterSlow(timeoutMs, new ThreadId((uint)currentManagedThreadId), associatedObject);
6357

6458
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6559
internal bool GetIsHeldByCurrentThread(int currentManagedThreadId)
6660
{
6761
Debug.Assert(currentManagedThreadId != 0);
6862

6963
bool isHeld = _owningThreadId == (uint)currentManagedThreadId;
70-
Debug.Assert(!isHeld || new State(this).IsLocked);
64+
Debug.Assert(!isHeld || this.IsLocked);
7165
return isHeld;
7266
}
7367

@@ -76,14 +70,9 @@ internal uint ExitAll()
7670
Debug.Assert(IsHeldByCurrentThread);
7771

7872
uint recursionCount = _recursionCount;
79-
_owningThreadId = 0;
8073
_recursionCount = 0;
8174

82-
State state = State.Unlock(this);
83-
if (state.HasAnyWaiters)
84-
{
85-
SignalWaiterIfNecessary(state);
86-
}
75+
ReleaseCore();
8776

8877
return recursionCount;
8978
}
@@ -96,108 +85,65 @@ internal void Reenter(uint previousRecursionCount)
9685
_recursionCount = previousRecursionCount;
9786
}
9887

99-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
100-
private TryLockResult LazyInitializeOrEnter()
88+
// Returns false until the static variable is lazy-initialized
89+
internal static bool IsSingleProcessor => s_processorCount == 1;
90+
91+
internal static void LazyInit()
10192
{
102-
StaticsInitializationStage stage = (StaticsInitializationStage)Volatile.Read(ref s_staticsInitializationStage);
103-
switch (stage)
93+
s_maxSpinCount = DefaultMaxSpinCount;
94+
s_minSpinCount = DefaultMinSpinCount;
95+
s_processorCount = RuntimeImports.RhGetProcessCpuCount();
96+
97+
// the rest is optional, but let's try once
98+
if (s_staticsInitializationStage != (int)StaticsInitializationStage.Complete)
10499
{
105-
case StaticsInitializationStage.Complete:
106-
if (_spinCount == SpinCountNotInitialized)
107-
{
108-
_spinCount = s_maxSpinCount;
109-
}
110-
return TryLockResult.Spin;
111-
112-
case StaticsInitializationStage.Started:
113-
// Spin-wait until initialization is complete or the lock is acquired to prevent class construction cycles
114-
// later during a full wait
115-
bool sleep = true;
116-
while (true)
117-
{
118-
if (sleep)
119-
{
120-
Thread.UninterruptibleSleep0();
121-
}
122-
else
123-
{
124-
Thread.SpinWait(1);
125-
}
126-
127-
stage = (StaticsInitializationStage)Volatile.Read(ref s_staticsInitializationStage);
128-
if (stage == StaticsInitializationStage.Complete)
129-
{
130-
goto case StaticsInitializationStage.Complete;
131-
}
132-
else if (stage == StaticsInitializationStage.NotStarted)
133-
{
134-
goto default;
135-
}
136-
137-
if (State.TryLock(this))
138-
{
139-
return TryLockResult.Locked;
140-
}
141-
142-
sleep = !sleep;
143-
}
144-
145-
default:
146-
Debug.Assert(stage == StaticsInitializationStage.NotStarted);
147-
if (TryInitializeStatics())
148-
{
149-
goto case StaticsInitializationStage.Complete;
150-
}
151-
goto case StaticsInitializationStage.Started;
100+
InitStatics();
152101
}
153102
}
154103

155104
[MethodImpl(MethodImplOptions.NoInlining)]
156-
private static bool TryInitializeStatics()
105+
internal static bool InitStatics()
157106
{
158-
// Since Lock is used to synchronize class construction, and some of the statics initialization may involve class
159-
// construction, update the stage first to avoid infinite recursion
160-
switch (
161-
(StaticsInitializationStage)
162-
Interlocked.CompareExchange(
163-
ref s_staticsInitializationStage,
164-
(int)StaticsInitializationStage.Started,
165-
(int)StaticsInitializationStage.NotStarted))
107+
if (s_staticsInitializationStage != (int)StaticsInitializationStage.Started)
166108
{
167-
case StaticsInitializationStage.Started:
168-
return false;
169-
case StaticsInitializationStage.Complete:
109+
// prevent reentrancy on the same thread
110+
s_staticsInitializationStage = (int)StaticsInitializationStage.Started;
111+
try
112+
{
113+
s_minSpinCount = DetermineMinSpinCount();
114+
s_maxSpinCount = DetermineMaxSpinCount();
115+
NativeRuntimeEventSource.Log.IsEnabled();
116+
117+
Volatile.Write(ref s_staticsInitializationStage, (int)StaticsInitializationStage.Complete);
170118
return true;
119+
}
120+
catch
121+
{
122+
// Callers can't handle this failure and also guarantee not coming here again because anything may take locks.
123+
// However initializing statics is optional, so just ignore the failure.
124+
s_staticsInitializationStage = (int)StaticsInitializationStage.NotStarted;
125+
}
171126
}
172127

173-
try
174-
{
175-
s_isSingleProcessor = Environment.IsSingleProcessor;
176-
s_maxSpinCount = DetermineMaxSpinCount();
177-
s_minSpinCount = DetermineMinSpinCount();
128+
return false;
129+
}
178130

179-
// Also initialize some types that are used later to prevent potential class construction cycles
180-
NativeRuntimeEventSource.Log.IsEnabled();
181-
}
182-
catch
131+
internal static bool StaticsInitComplete()
132+
{
133+
if (Volatile.Read(ref s_staticsInitializationStage) == (int)StaticsInitializationStage.Complete)
183134
{
184-
s_staticsInitializationStage = (int)StaticsInitializationStage.NotStarted;
185-
throw;
135+
return true;
186136
}
187137

188-
Volatile.Write(ref s_staticsInitializationStage, (int)StaticsInitializationStage.Complete);
189-
return true;
138+
return InitStatics();
190139
}
191140

192-
// Returns false until the static variable is lazy-initialized
193-
internal static bool IsSingleProcessor => s_isSingleProcessor;
194-
195141
// Used to transfer the state when inflating thin locks
196142
internal void InitializeLocked(int managedThreadId, uint recursionCount)
197143
{
198144
Debug.Assert(recursionCount == 0 || managedThreadId != 0);
199145

200-
_state = managedThreadId == 0 ? State.InitialStateValue : State.LockedStateValue;
146+
_state = managedThreadId == 0 ? Unlocked : Locked;
201147
_owningThreadId = (uint)managedThreadId;
202148
_recursionCount = recursionCount;
203149
}

src/libraries/System.Private.CoreLib/src/System/Threading/Lock.NonNativeAot.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ public sealed partial class Lock
1111
private static readonly short s_maxSpinCount = DetermineMaxSpinCount();
1212
private static readonly short s_minSpinCount = DetermineMinSpinCount();
1313

14-
/// <summary>
15-
/// Initializes a new instance of the <see cref="Lock"/> class.
16-
/// </summary>
17-
public Lock() => _spinCount = s_maxSpinCount;
18-
19-
private static TryLockResult LazyInitializeOrEnter() => TryLockResult.Spin;
14+
private static void LazyInit() { }
15+
private static bool StaticsInitComplete() => true;
2016
private static bool IsSingleProcessor => Environment.IsSingleProcessor;
2117

2218
internal partial struct ThreadId

0 commit comments

Comments
 (0)