Issues related to Environment.ProcessorCount calculation #67308
Replies: 4 comments 4 replies
-
Beta Was this translation helpful? Give feedback.
-
Agree. Should we also stop artificially distributing threads across CPU groups and just let Win 11+ OSes deal with it? https://github.com/dotnet/runtime/search?q=ChooseThreadCPUGroupAffinity
Double-check this assumption. It is not obvious to me that this should be impossible.
Does the 64-bit CPU-group based algorithm is going to produce the correct results on 32-bit platforms? If yes, we can just use that instead of having a special path for 32-bit that uses GetLogicalProcessorInformationEx.
Should we completely ignore these settings on Win11+ OSes? We may also want change the default on pre-Win11 OSes so that the .NET runtime behavior is consistent between OS versions (ie finally fix #13465). |
Beta Was this translation helpful? Give feedback.
-
Last I saw, there was an issue with that when using server GC. When threads in the process are affinitized to processors from all CPU groups (GC threads), the scheduler seems to revert to round-robin scheduling of other threads in the process, which kind of defeats the benefit. I think it would be a good thing to do if GC threads were not to be affinitized to specific processors, or to look into a fix to the scheduler.
The behavior would be similar between Win10 and Win11 when these settings are enabled. The main difference would be for threads created from outside the runtime and enter the runtime, the runtime doesn't do any thread-spreading between groups for them, so they by default on Win10 would be confined to the primary CPU group for the process and on Win11 would not. I don't think it's a significant difference from the runtime's perspective, there has already been a case where the runtime's thread-spreading was disabled through config for the process to better balance CPU usage between threads created by the runtime and threads created from outside. |
Beta Was this translation helpful? Give feedback.
-
Below are suggestions I received from the OS team. Steps 1–3 are pretty much what I suggested above. At present CLR disrespects affinity restrictions set on the job object (step 4,
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Below is how we calculate the processor count (
Environment.ProcessorCount
) on Windows at present:DOTNET_PROCESSOR_COUNT
setting is defined and its value is in the [1,0xffff] range, use that value and skip all the steps below. This setting overrides everything.DOTNET_GCCpuGroup
andDOTNET_Thread_UseAllCpuGroups
are set, and the machine has more than one processor group, use the number of active processors across all processor groups — callGetLogicalProcessorInformationEx(RelationGroup)
and sumActiveProcessorCount
fields for all returned groups. Go to step C.GetProcessAffinityMask
and use the number of bits set in the process affinity mask if it is not zero. Go to step C.Here is the list of issues with the current approach:
I1. Starting with Windows 11 and Windows Server 2022, on a system with more than 64 processors, process and thread affinities span all processors in the system, across all processor groups, by default. The
GetProcessAffinityMask
function returns the process affinity mask for the calling thread's primary group (which by default is the same as the process' primary group). That means step B2 may return too small values. For instance, on an 80-core machine with two processor groups (64+16 processors), step B2 returns 16 for a half of 64-bit processes and 64 for the other half, while all 80 processors are available to each process.I2. For 32-bit processes
GetProcessAffinityMask
caps the returned process affinity mask to 32 bits, which may cause step B2 to return too small values. For instance, on a 64-core machine with a single processor group, step B2 may return 32, while all 64 processors are available.I3. For 32-bit processes querying processor topology in step B1 is never performed,
DOTNET_GCCpuGroup
andDOTNET_Thread_UseAllCpuGroups
settings are ignored. That means we never use the correct value on machines with several processor groups.Noteworthy, we used to have
correctprocessor group-aware calculation of the processor count for 32-bit processes, which was removed during refactoring and reused in MSBuild sources as a workaround.My thoughts on this:
We should fix behavior of 64-bit processes on Windows 11. We may use
RtlGetVersion
to get Windows version to avoid compatibility shimming since the new behavior is enabled even if you set the executable's compatibility to one of previous Windows versions. We may useGetProcessGroupAffinity
to get the list of processor groups available to the process and calculate the sum ofActiveProcessorCount
fields for them. My assumption is that it is not possible to impose both processor group affinity and processor affinity within a group at the same time.We may want to fix calculation of the processor count for 32-bit processes by
restoring the logic that usedusingGetLogicalProcessorInformationEx(RelationGroup)
GetLogicalProcessorInformationEx(RelationProcessorCore)
, which is not subject to capping.Note that we have an old work item to enable using all processor groups by default on all supported Windows versions, which has been postponed several times.
EDIT: Clarified that 32-bit processes should use
GetLogicalProcessorInformationEx(RelationProcessorCore)
to get the correct processor count.I also suspect that case B3 (
GetProcessAffinityMask
returning the zero process affinity mask) is not possible in Windows 11 according to the documentation.Beta Was this translation helpful? Give feedback.
All reactions