Skip to content

Commit 316eab8

Browse files
authored
Make CPU utilization checks in the thread pool configurable (#112790)
- On Windows, checking CPU utilization seems to involve a small amount of overhead, which can become noticeable or even significant in some scenarios. This change makes the intervals of time over which CPU utilization is computed configurable. Increasing the interval increases the period at which CPU utilization is updated. The same config var can also be used to disable CPU utilization checks and have features that use it behave as though CPU utilization is low. - CPU utilization is used by the starvation heuristic and hill climbing. When CPU utilization is very high, the starvation heuristic reduces the rate of thread injection in starved cases. When CPU utilization is high, hill climbing avoids settling on higher thread count control values. - CPU utilization is currently updated when the gate thread performs periodic activities, which happens typically every 500 ms when a worker thread is active. There is one gate thread per .NET process. - In scenarios where there are many .NET processes running, and where many of them frequently but lightly use the thread pool, overall CPU usage may be relatively low, but the overhead from CPU utilization checks can bubble up to a noticeable portion of overall CPU usage. In a scenario involving 100s of .NET processes, it was seen that CPU utilization checks amount to 0.5-1% of overall CPU usage on the machine, which was considered significant.
1 parent be6cf76 commit 316eab8

File tree

1 file changed

+28
-3
lines changed

1 file changed

+28
-3
lines changed

src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.GateThread.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,26 @@ private static void GateThreadStart()
2727
bool debuggerBreakOnWorkStarvation =
2828
AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.DebugBreakOnWorkerStarvation", false);
2929

30+
// CPU utilization is updated when the gate thread performs periodic activities (GateActivitiesPeriodMs), so
31+
// that would also affect the actual interval. Set to 0 to disable using CPU utilization and have components
32+
// behave as though CPU utilization is low. The default value of 1 causes CPU utilization to be updated whenever
33+
// the gate thread performs periodic activities.
34+
int cpuUtilizationIntervalMs =
35+
AppContextConfigHelper.GetInt32Config(
36+
"System.Threading.ThreadPool.CpuUtilizationIntervalMs",
37+
"DOTNET_ThreadPool_CpuUtilizationIntervalMs",
38+
defaultValue: 1,
39+
allowNegative: false);
40+
3041
// The first reading is over a time range other than what we are focusing on, so we do not use the read other
3142
// than to send it to any runtime-specific implementation that may also use the CPU utilization.
3243
CpuUtilizationReader cpuUtilizationReader = default;
33-
_ = cpuUtilizationReader.CurrentUtilization;
44+
int lastCpuUtilizationRefreshTimeMs = 0;
45+
if (cpuUtilizationIntervalMs > 0)
46+
{
47+
lastCpuUtilizationRefreshTimeMs = Environment.TickCount;
48+
_ = cpuUtilizationReader.CurrentUtilization;
49+
}
3450

3551
PortableThreadPool threadPoolInstance = ThreadPoolInstance;
3652
LowLevelLock threadAdjustmentLock = threadPoolInstance._threadAdjustmentLock;
@@ -102,8 +118,17 @@ private static void GateThreadStart()
102118
(uint)threadPoolInstance.GetAndResetHighWatermarkCountOfThreadsProcessingUserCallbacks());
103119
}
104120

105-
int cpuUtilization = (int)cpuUtilizationReader.CurrentUtilization;
106-
threadPoolInstance._cpuUtilization = cpuUtilization;
121+
// Determine whether CPU utilization should be updated. CPU utilization is only used by the starvation
122+
// heuristic and hill climbing, and neither of those are active when there is a pending blocking
123+
// adjustment.
124+
if (cpuUtilizationIntervalMs > 0 &&
125+
threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None &&
126+
(uint)(currentTimeMs - lastCpuUtilizationRefreshTimeMs) >= (uint)cpuUtilizationIntervalMs)
127+
{
128+
lastCpuUtilizationRefreshTimeMs = currentTimeMs;
129+
int cpuUtilization = (int)cpuUtilizationReader.CurrentUtilization;
130+
threadPoolInstance._cpuUtilization = cpuUtilization;
131+
}
107132

108133
if (!disableStarvationDetection &&
109134
threadPoolInstance._pendingBlockingAdjustment == PendingBlockingAdjustment.None &&

0 commit comments

Comments
 (0)