Add Thread.GetCurrentProcessorId() API (#16650)
authorJan Kotas <jkotas@microsoft.com>
Wed, 28 Feb 2018 15:22:29 +0000 (07:22 -0800)
committerGitHub <noreply@github.com>
Wed, 28 Feb 2018 15:22:29 +0000 (07:22 -0800)
Contributes to https://github.com/dotnet/corefx/issues/16767

src/classlibnative/bcltype/system.cpp
src/classlibnative/bcltype/system.h
src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
src/mscorlib/src/Internal/Runtime/Augments/RuntimeThread.cs
src/mscorlib/src/System/Environment.cs
src/mscorlib/src/System/Threading/Timer.cs
src/vm/comsynchronizable.cpp
src/vm/comsynchronizable.h
src/vm/ecalllist.h

index fdb0416..91481d7 100644 (file)
@@ -100,15 +100,6 @@ FCIMPLEND;
 
 
 
-FCIMPL0(UINT32, SystemNative::GetCurrentProcessorNumber)
-{
-    FCALL_CONTRACT;
-
-    return ::GetCurrentProcessorNumber();
-}
-FCIMPLEND;
-
-
 
 FCIMPL0(UINT32, SystemNative::GetTickCount)
 {
index 6a489ba..9f985bf 100644 (file)
@@ -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
index 64c5ceb..f6affca 100644 (file)
@@ -6,6 +6,8 @@ using Microsoft.Win32;
 using System.Runtime.CompilerServices;
 using System.Threading;
 
+using Internal.Runtime.Augments;
+
 namespace System.Buffers
 {
     /// <summary>
@@ -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;
index 17fa69e..4678be6 100644 (file)
@@ -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();
 
index a4d5aec..a246d1b 100644 (file)
@@ -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)
index 90dd207..a58c288 100644 (file)
@@ -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
index 1956121..c0d31f0 100644 (file)
@@ -1768,3 +1768,10 @@ FCIMPL1(void, ThreadNative::ClearAbortReason, ThreadBaseObject* pThisUNSAFE)
 FCIMPLEND
 
 
+FCIMPL0(INT32, ThreadNative::GetCurrentProcessorNumber)
+{
+    FCALL_CONTRACT;
+
+    return ::GetCurrentProcessorNumber();
+}
+FCIMPLEND;
index b280c60..589fce3 100644 (file)
@@ -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 {
index 1259759..460ebf6 100644 (file)
@@ -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)