Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Move GetCurrentProcessorId to shared and mod with ProcCount
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams committed Oct 31, 2019
1 parent c747523 commit bb8c0b9
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 58 deletions.
11 changes: 0 additions & 11 deletions src/System.Private.CoreLib/shared/System/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;

namespace System
Expand All @@ -19,16 +18,6 @@ public static partial class Environment
/// </summary>
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;

Expand Down
50 changes: 50 additions & 0 deletions src/System.Private.CoreLib/shared/System/Threading/Thread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/System.Private.CoreLib/shared/System/Threading/Timer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
46 changes: 0 additions & 46 deletions src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit bb8c0b9

Please sign in to comment.