diff --git a/src/System.Private.CoreLib/shared/System/Environment.cs b/src/System.Private.CoreLib/shared/System/Environment.cs index 0e68ea45692b..1454f1d56e40 100644 --- a/src/System.Private.CoreLib/shared/System/Environment.cs +++ b/src/System.Private.CoreLib/shared/System/Environment.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Diagnostics; using System.Reflection; -using System.Runtime.CompilerServices; using System.Threading; namespace System @@ -19,16 +18,6 @@ public static partial class Environment /// internal static bool IsSingleProcessor => ProcessorCount == 1; - internal static int ProcessorIdModCount - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - // As ProcessorCount will be a constant at Tier1; the Jit will drop the mod and use a faster approach. - return Thread.GetCurrentProcessorId() % ProcessorCount; - } - } - // Unconditionally return false since .NET Core does not support object finalization during shutdown. public static bool HasShutdownStarted => false; diff --git a/src/System.Private.CoreLib/shared/System/Threading/Thread.cs b/src/System.Private.CoreLib/shared/System/Threading/Thread.cs index ca1d94f6fdf2..12a55adf9385 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Thread.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Thread.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Security.Principal; @@ -210,6 +211,55 @@ public static void EndCriticalRegion() { } public static void BeginThreadAffinity() { } public static void EndThreadAffinity() { } + // The upper bits of t_currentProcessorIdCache are the currentProcessorId. The lower bits of + // the t_currentProcessorIdCache are counting down to get it periodically refreshed. + // TODO: Consider flushing the currentProcessorIdCache on Wait operations or similar + // actions that are likely to result in changing the executing core + [ThreadStatic] + private static int t_currentProcessorIdCache; + + private const int ProcessorIdCacheShift = 16; + private const int ProcessorIdCacheCountDownMask = (1 << ProcessorIdCacheShift) - 1; + private const int ProcessorIdRefreshRate = 5000; + + private static int RefreshCurrentProcessorId() + { + int currentProcessorId = GetCurrentProcessorNumber(); + + // If GetCurrentProcessorNumber() is not fully implemented it will return -1. + // On Unix, GetCurrentProcessorNumber() is implemented in terms of sched_getcpu, which + // doesn't exist on all platforms. On those it doesn't exist on, GetCurrentProcessorNumber() + // returns -1. As a fallback in that case and to spread the threads across the buckets + // by default, we use the current managed thread ID as a proxy. + if (currentProcessorId < 0) currentProcessorId = Environment.CurrentManagedThreadId; + + // Ensure the Id is in range of the ProcessorCount + currentProcessorId = (int)((uint)currentProcessorId % (uint)Environment.ProcessorCount); + + Debug.Assert(ProcessorIdRefreshRate <= ProcessorIdCacheCountDownMask); + + // Mask with int.MaxValue to ensure the execution Id is not negative + t_currentProcessorIdCache = ((currentProcessorId << ProcessorIdCacheShift) & int.MaxValue) | ProcessorIdRefreshRate; + + return currentProcessorId; + } + + // Cached processor id used as a hint for which per-core stack to access. It is periodically + // refreshed to trail the actual thread core affinity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetCurrentProcessorId() + { + int currentProcessorIdCache = t_currentProcessorIdCache--; + if ((currentProcessorIdCache & ProcessorIdCacheCountDownMask) == 0) + { + return RefreshCurrentProcessorId(); + } + + int processorId = currentProcessorIdCache >> ProcessorIdCacheShift; + Debug.Assert(processorId >= 0 && processorId < Environment.ProcessorCount); + return processorId; + } + public static LocalDataStoreSlot AllocateDataSlot() => LocalDataStore.AllocateSlot(); public static LocalDataStoreSlot AllocateNamedDataSlot(string name) => LocalDataStore.AllocateNamedSlot(name); public static LocalDataStoreSlot GetNamedDataSlot(string name) => LocalDataStore.GetNamedSlot(name); diff --git a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs index e82af276e21a..69818060e4fb 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs @@ -43,8 +43,10 @@ internal partial class TimerQueue public static TimerQueue GetQueueForProcessor() { + int index = Thread.GetCurrentProcessorId(); Debug.Assert(Environment.ProcessorCount == Instances.Length); - return Instances[Environment.ProcessorIdModCount]; + Debug.Assert(index >= 0 && index < Instances.Length); + return Instances[index]; } private static TimerQueue[] CreateTimerQueues() diff --git a/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 5e3899a92dfb..0548c5877285 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -487,52 +487,6 @@ private static int CalculateOptimalMaxSpinWaitsPerSpinIteration() [MethodImpl(MethodImplOptions.InternalCall)] private static extern int GetCurrentProcessorNumber(); - // The upper bits of t_currentProcessorIdCache are the currentProcessorId. The lower bits of - // the t_currentProcessorIdCache are counting down to get it periodically refreshed. - // TODO: Consider flushing the currentProcessorIdCache on Wait operations or similar - // actions that are likely to result in changing the executing core - [ThreadStatic] - private static int t_currentProcessorIdCache; - - private const int ProcessorIdCacheShift = 16; - private const int ProcessorIdCacheCountDownMask = (1 << ProcessorIdCacheShift) - 1; - private const int ProcessorIdRefreshRate = 5000; - - private static int RefreshCurrentProcessorId() - { - int currentProcessorId = GetCurrentProcessorNumber(); - - // On Unix, GetCurrentProcessorNumber() is implemented in terms of sched_getcpu, which - // doesn't exist on all platforms. On those it doesn't exist on, GetCurrentProcessorNumber() - // returns -1. As a fallback in that case and to spread the threads across the buckets - // by default, we use the current managed thread ID as a proxy. - if (currentProcessorId < 0) currentProcessorId = Environment.CurrentManagedThreadId; - - // Add offset to make it clear that it is not guaranteed to be 0-based processor number - currentProcessorId += 100; - - Debug.Assert(ProcessorIdRefreshRate <= ProcessorIdCacheCountDownMask); - - // Mask with int.MaxValue to ensure the execution Id is not negative - t_currentProcessorIdCache = ((currentProcessorId << ProcessorIdCacheShift) & int.MaxValue) | ProcessorIdRefreshRate; - - return currentProcessorId; - } - - // Cached processor id used as a hint for which per-core stack to access. It is periodically - // refreshed to trail the actual thread core affinity. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetCurrentProcessorId() - { - int currentProcessorIdCache = t_currentProcessorIdCache--; - if ((currentProcessorIdCache & ProcessorIdCacheCountDownMask) == 0) - { - return RefreshCurrentProcessorId(); - } - - return currentProcessorIdCache >> ProcessorIdCacheShift; - } - internal void ResetThreadPoolThread() { // Currently implemented in unmanaged method Thread::InternalReset and