From f0d528126a04ab611bc0a0175928b2baf5537f79 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 26 Sep 2023 14:32:34 -0700 Subject: [PATCH 1/2] Convert SpinWait to QCall --- .../src/System/Threading/Thread.CoreCLR.cs | 27 ++++++++++++++++--- src/coreclr/vm/comsynchronizable.cpp | 23 +--------------- src/coreclr/vm/comsynchronizable.h | 1 + src/coreclr/vm/ecalllist.h | 1 - src/coreclr/vm/qcallentrypoints.cpp | 1 + 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 889bb0d492c74..b305eb5f89bce 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -118,15 +118,34 @@ private void StartCallback() [MethodImpl(MethodImplOptions.InternalCall)] private static extern void SleepInternal(int millisecondsTimeout); + // Max iterations to be done in SpinWait without switching GC modes. + private const int SpinWaitCoopThreshold = 1024; + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SpinWait")] + private static partial void SpinWaitInternal(int iterations); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SpinWait")] + private static partial void LongSpinWaitInternal(int iterations); + + [MethodImpl(MethodImplOptions.NoInlining)] // Slow path method. Make sure that the caller frame does not pay for PInvoke overhead. + private static void LongSpinWait(int iterations) => LongSpinWaitInternal(iterations); + /// /// Wait for a length of time proportional to 'iterations'. Each iteration is should /// only take a few machine instructions. Calling this API is preferable to coding /// a explicit busy loop because the hardware can be informed that it is busy waiting. /// - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void SpinWaitInternal(int iterations); - - public static void SpinWait(int iterations) => SpinWaitInternal(iterations); + public static void SpinWait(int iterations) + { + if (iterations < SpinWaitCoopThreshold) + { + SpinWaitInternal(iterations); + } + else + { + LongSpinWait(iterations); + } + } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_YieldThread")] private static partial Interop.BOOL YieldInternal(); diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 7df5589fea22d..35ddf6d04c1b8 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -1035,7 +1035,7 @@ FCIMPL0(INT32, ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) } FCIMPLEND -FCIMPL1(void, ThreadNative::SpinWait, int iterations) +extern "C" void QCALLTYPE ThreadNative_SpinWait(INT32 iterations) { FCALL_CONTRACT; @@ -1044,29 +1044,8 @@ FCIMPL1(void, ThreadNative::SpinWait, int iterations) return; } - // - // If we're not going to spin for long, it's ok to remain in cooperative mode. - // The threshold is determined by the cost of entering preemptive mode; if we're - // spinning for less than that number of cycles, then switching to preemptive - // mode won't help a GC start any faster. - // - if (iterations <= 100000) - { - YieldProcessorNormalized(iterations); - return; - } - - // - // Too many iterations; better switch to preemptive mode to avoid stalling a GC. - // - HELPER_METHOD_FRAME_BEGIN_NOPOLL(); - GCX_PREEMP(); - YieldProcessorNormalized(iterations); - - HELPER_METHOD_FRAME_END(); } -FCIMPLEND extern "C" BOOL QCALLTYPE ThreadNative_YieldThread() { diff --git a/src/coreclr/vm/comsynchronizable.h b/src/coreclr/vm/comsynchronizable.h index a069d109a79d1..a174e2cedf13d 100644 --- a/src/coreclr/vm/comsynchronizable.h +++ b/src/coreclr/vm/comsynchronizable.h @@ -101,6 +101,7 @@ extern "C" BOOL QCALLTYPE ThreadNative_YieldThread(); extern "C" UINT64 QCALLTYPE ThreadNative_GetCurrentOSThreadId(); extern "C" void QCALLTYPE ThreadNative_Abort(QCall::ThreadHandle thread); extern "C" void QCALLTYPE ThreadNative_ResetAbort(); +extern "C" void QCALLTYPE ThreadNative_SpinWait(INT32 iterations); #endif // _COMSYNCHRONIZABLE_H diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index e4663c025e6c6..2f1f27bdf3eee 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -366,7 +366,6 @@ FCFuncStart(gThreadFuncs) FCFuncElement("InternalGetCurrentThread", GetThread) FCFuncElement("SleepInternal", ThreadNative::Sleep) FCFuncElement("Initialize", ThreadNative::Initialize) - FCFuncElement("SpinWaitInternal", ThreadNative::SpinWait) FCFuncElement("GetCurrentThreadNative", ThreadNative::GetCurrentThread) FCFuncElement("InternalFinalize", ThreadNative::Finalize) FCFuncElement("get_IsAlive", ThreadNative::IsAlive) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 0725bd7a87f09..deccf3e1a852e 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -204,6 +204,7 @@ static const Entry s_QCall[] = DllImportEntry(ThreadNative_GetCurrentOSThreadId) DllImportEntry(ThreadNative_Abort) DllImportEntry(ThreadNative_ResetAbort) + DllImportEntry(ThreadNative_SpinWait) #ifdef TARGET_UNIX DllImportEntry(WaitHandle_CorWaitOnePrioritizedNative) #endif From ed595a3ea097845b63fa26f92c4f2995aa743113 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 26 Sep 2023 15:44:16 -0700 Subject: [PATCH 2/2] Add SuppressGCTransition --- .../src/System/Threading/Thread.CoreCLR.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index b305eb5f89bce..13d0a426df19f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -122,6 +122,7 @@ private void StartCallback() private const int SpinWaitCoopThreshold = 1024; [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SpinWait")] + [SuppressGCTransition] private static partial void SpinWaitInternal(int iterations); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SpinWait")]