From 29d5dc25ab6fd078cc349e7f7e21aaeacda59acc Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Wed, 28 Feb 2018 07:22:29 -0800 Subject: [PATCH] Add Thread.GetCurrentProcessorId() API (#16650) Contributes to https://github.com/dotnet/corefx/issues/16767 --- src/classlibnative/bcltype/system.cpp | 9 ---- src/classlibnative/bcltype/system.h | 1 - .../Buffers/TlsOverPerCoreLockedStacksArrayPool.cs | 6 ++- .../src/Internal/Runtime/Augments/RuntimeThread.cs | 46 ++++++++++++++++++++ src/mscorlib/src/System/Environment.cs | 49 ---------------------- src/mscorlib/src/System/Threading/Timer.cs | 28 ++++++------- src/vm/comsynchronizable.cpp | 7 ++++ src/vm/comsynchronizable.h | 2 + src/vm/ecalllist.h | 2 +- 9 files changed, 73 insertions(+), 77 deletions(-) diff --git a/src/classlibnative/bcltype/system.cpp b/src/classlibnative/bcltype/system.cpp index fdb0416..91481d7 100644 --- a/src/classlibnative/bcltype/system.cpp +++ b/src/classlibnative/bcltype/system.cpp @@ -100,15 +100,6 @@ FCIMPLEND; -FCIMPL0(UINT32, SystemNative::GetCurrentProcessorNumber) -{ - FCALL_CONTRACT; - - return ::GetCurrentProcessorNumber(); -} -FCIMPLEND; - - FCIMPL0(UINT32, SystemNative::GetTickCount) { diff --git a/src/classlibnative/bcltype/system.h b/src/classlibnative/bcltype/system.h index 6a489ba..9f985bf 100644 --- a/src/classlibnative/bcltype/system.h +++ b/src/classlibnative/bcltype/system.h @@ -39,7 +39,6 @@ private: public: // Functions on the System.Environment class static FCDECL0(INT64, __GetSystemTimeAsFileTime); - static FCDECL0(UINT32, GetCurrentProcessorNumber); static FCDECL0(UINT32, GetTickCount); static diff --git a/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs index 64c5ceb..f6affca 100644 --- a/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs +++ b/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -6,6 +6,8 @@ using Microsoft.Win32; using System.Runtime.CompilerServices; using System.Threading; +using Internal.Runtime.Augments; + namespace System.Buffers { /// @@ -228,7 +230,7 @@ namespace System.Buffers // Try to push on to the associated stack first. If that fails, // round-robin through the other stacks. LockedStack[] stacks = _perCoreStacks; - int index = Environment.CurrentExecutionId % stacks.Length; + int index = RuntimeThread.GetCurrentProcessorId() % stacks.Length; for (int i = 0; i < stacks.Length; i++) { if (stacks[index].TryPush(array)) return; @@ -244,7 +246,7 @@ namespace System.Buffers // round-robin through the other stacks. T[] arr; LockedStack[] stacks = _perCoreStacks; - int index = Environment.CurrentExecutionId % stacks.Length; + int index = RuntimeThread.GetCurrentProcessorId() % stacks.Length; for (int i = 0; i < stacks.Length; i++) { if ((arr = stacks[index].TryPop()) != null) return arr; diff --git a/src/mscorlib/src/Internal/Runtime/Augments/RuntimeThread.cs b/src/mscorlib/src/Internal/Runtime/Augments/RuntimeThread.cs index 17fa69e..4678be6 100644 --- a/src/mscorlib/src/Internal/Runtime/Augments/RuntimeThread.cs +++ b/src/mscorlib/src/Internal/Runtime/Augments/RuntimeThread.cs @@ -213,6 +213,52 @@ namespace Internal.Runtime.Augments } } + [MethodImplAttribute(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 Int32.MaxValue to ensure the execution Id is not negative + t_currentProcessorIdCache = ((currentProcessorId << ProcessorIdCacheShift) & Int32.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); + } + public static void SpinWait(int iterations) => Thread.SpinWait(iterations); public static bool Yield() => Thread.Yield(); diff --git a/src/mscorlib/src/System/Environment.cs b/src/mscorlib/src/System/Environment.cs index a4d5aec..a246d1b 100644 --- a/src/mscorlib/src/System/Environment.cs +++ b/src/mscorlib/src/System/Environment.cs @@ -367,55 +367,6 @@ namespace System } } - internal static extern int CurrentProcessorNumber - { - [MethodImplAttribute(MethodImplOptions.InternalCall)] - get; - } - - // The upper bits of t_executionIdCache are the executionId. The lower bits of - // the t_executionIdCache are counting down to get it periodically refreshed. - // TODO: Consider flushing the executionIdCache on Wait operations or similar - // actions that are likely to result in changing the executing core - [ThreadStatic] - private static int t_executionIdCache; - - private const int ExecutionIdCacheShift = 16; - private const int ExecutionIdCacheCountDownMask = (1 << ExecutionIdCacheShift) - 1; - private const int ExecutionIdRefreshRate = 5000; - - private static int RefreshExecutionId() - { - int executionId = CurrentProcessorNumber; - - // On Unix, CurrentProcessorNumber 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 (executionId < 0) executionId = Environment.CurrentManagedThreadId; - - Debug.Assert(ExecutionIdRefreshRate <= ExecutionIdCacheCountDownMask); - - // Mask with Int32.MaxValue to ensure the execution Id is not negative - t_executionIdCache = ((executionId << ExecutionIdCacheShift) & Int32.MaxValue) | ExecutionIdRefreshRate; - - return executionId; - } - - // Cached processor number used as a hint for which per-core stack to access. It is periodically - // refreshed to trail the actual thread core affinity. - internal static int CurrentExecutionId - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - int executionIdCache = t_executionIdCache--; - if ((executionIdCache & ExecutionIdCacheCountDownMask) == 0) - return RefreshExecutionId(); - return (executionIdCache >> ExecutionIdCacheShift); - } - } - public static string GetEnvironmentVariable(string variable) { if (variable == null) diff --git a/src/mscorlib/src/System/Threading/Timer.cs b/src/mscorlib/src/System/Threading/Timer.cs index 90dd207..a58c288 100644 --- a/src/mscorlib/src/System/Threading/Timer.cs +++ b/src/mscorlib/src/System/Threading/Timer.cs @@ -2,23 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// +using System; +using System.Security; +using Microsoft.Win32; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using Microsoft.Win32.SafeHandles; + +using Internal.Runtime.Augments; namespace System.Threading { - using System; - using System.Security; - using Microsoft.Win32; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using System.Runtime.ConstrainedExecution; - using System.Runtime.Versioning; - using System.Diagnostics; - using System.Diagnostics.Tracing; - using Microsoft.Win32.SafeHandles; - - - public delegate void TimerCallback(Object state); // @@ -449,7 +447,7 @@ namespace System.Threading m_dueTime = Timeout.UnsignedInfinite; m_period = Timeout.UnsignedInfinite; m_executionContext = ExecutionContext.Capture(); - m_associatedTimerQueue = TimerQueue.Instances[Environment.CurrentExecutionId % TimerQueue.Instances.Length]; + m_associatedTimerQueue = TimerQueue.Instances[RuntimeThread.GetCurrentProcessorId() % TimerQueue.Instances.Length]; // // After the following statement, the timer may fire. No more manipulation of timer state outside of diff --git a/src/vm/comsynchronizable.cpp b/src/vm/comsynchronizable.cpp index 1956121..c0d31f0 100644 --- a/src/vm/comsynchronizable.cpp +++ b/src/vm/comsynchronizable.cpp @@ -1768,3 +1768,10 @@ FCIMPL1(void, ThreadNative::ClearAbortReason, ThreadBaseObject* pThisUNSAFE) FCIMPLEND +FCIMPL0(INT32, ThreadNative::GetCurrentProcessorNumber) +{ + FCALL_CONTRACT; + + return ::GetCurrentProcessorNumber(); +} +FCIMPLEND; diff --git a/src/vm/comsynchronizable.h b/src/vm/comsynchronizable.h index b280c60..589fce3 100644 --- a/src/vm/comsynchronizable.h +++ b/src/vm/comsynchronizable.h @@ -112,6 +112,8 @@ public: static FCDECL2(void, SetAbortReason, ThreadBaseObject* pThisUNSAFE, Object* pObject); static FCDECL1(void, ClearAbortReason, ThreadBaseObject* pThisUNSAFE); + static FCDECL0(INT32, GetCurrentProcessorNumber); + private: struct KickOffThread_Args { diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 1259759..460ebf6 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -156,7 +156,6 @@ FCFuncStart(gEnvironmentFuncs) FCFuncElement("get_HasShutdownStarted", SystemNative::HasShutdownStarted) QCFuncElement("GetProcessorCount", SystemNative::GetProcessorCount) FCFuncElement("GetCommandLineArgsNative", SystemNative::GetCommandLineArgs) - FCFuncElement("get_CurrentProcessorNumber", SystemNative::GetCurrentProcessorNumber) #if defined(FEATURE_COMINTEROP) QCFuncElement("WinRTSupported", SystemNative::WinRTSupported) @@ -690,6 +689,7 @@ FCFuncStart(gRuntimeThreadFuncs) FCFuncElement("InterruptInternal", ThreadNative::Interrupt) FCFuncElement("JoinInternal", ThreadNative::Join) QCFuncElement("GetOptimalMaxSpinWaitsPerSpinIterationInternal", ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) + FCFuncElement("GetCurrentProcessorNumber", ThreadNative::GetCurrentProcessorNumber) FCFuncEnd() FCFuncStart(gThreadFuncs) -- 2.7.4