Skip to content

[Breaking change] Environment.ProcessorCount on Windows takes processor affinity into account #47427

@kouvel

Description

@kouvel

When the process is affinitized on startup, the behavior of Environment.ProcessorCount currently differs between Windows and Linux. On Linux, the property returns the number of processors available according to the affinity mask. On Windows, the property returns the number of processors configured in the system and ignores the affinity mask. The property is often used to determine the amount of parallelism to use in the process, and it has been observed that not limiting the property's value based on affinitization can lead to worse performance.

On Linux, CPU resource limits placed through cgroups also affect the value returned by the property, as we have found through trial-and-error that returning the restricted number of processors from this property is the most appropriate behavior that makes most callers work well.

Goal

  • Have Environment.ProcessorCount return a value that represents an appropriate level of parallelism that may be used in the process based on affinitization and CPU resource limits
  • Have the property return a consistent value in comparable relevant configuration between Windows and Linux

Behavior before change

On a machine with 8 logical processors:

  • When the process is affinitized to 1 logical processor:
    • Windows: Environment.ProcessorCount == 8
    • Linux: Environment.ProcessorCount == 1
  • When CPU resources for the process are limited to the equivalent of 1 logical processor:
    • Windows: Environment.ProcessorCount == 8
    • Linux: Environment.ProcessorCount == 1

Behavior after #45943

  • When the process is affinitized to 1 logical processor:
    • Windows: Environment.ProcessorCount == 1
    • Linux: Environment.ProcessorCount == 1
  • When CPU resources for the process are limited to the equivalent of 1 logical processor:
    • Windows: Environment.ProcessorCount == 8
    • Linux: Environment.ProcessorCount == 1

Behavior after other such issues are fixed (no PR yet)

  • When the process is affinitized to 1 logical processor:
    • Windows: Environment.ProcessorCount == 1
    • Linux: Environment.ProcessorCount == 1
  • When CPU resources for the process are limited to the equivalent of 1 logical processor:
    • Windows: Environment.ProcessorCount == 1
    • Linux: Environment.ProcessorCount == 1

Versions affected

.NET 6

Potential breaks on Windows and workarounds

  • Code that uses Environment.ProcessorCount and scales down parallelism based on other app or system configuration (including potentially the process' affinity mask) may end up using lower parallelism than intended
    • Workaround may be to update the application's parallelism scaling
  • Code that expects Environment.ProcessorCount to return the configured logical processor count may have unexpected results. For example code that verifies that the process is not started affinitized by comparing the affinitized processor count to Environment.ProcessorCount would fail to detect affinitized startup because the comparison would be equal. Or the process may intend to display the total processor count.
    • Workaround may be to pinvoke to get the total processor count. May not be ideal. Providing full information in other APIs would need discussion to determine what to provide, for example, number of processors configured in the system, number of processors that are online/offline, number of processors the process is affinitized to, CPU quota limits, etc.
  • Code that happens to perform worse when the value returned by Environment.ProcessorCount is limited by affinitization or CPU resource limits
    • If these become common, it may be necessary to provide a workaround, such as to provide a way to configure what Environment.ProcessorCount would return, or to provide other APIs

Currently, a config option is not provided to revert the behavior. The difference in behavior between Windows and Linux existed in .NET 5 and below, where Linux has not offered a config option to provide the total processor count, so there hasn't been a workaround for the above issues on Linux so far.

Non-breaking

  • The thread pool already took affinitization into account in the default parallelization it uses on Windows, when the process is not configured to use multiple CPU groups (the default). In .NET 6 thread pool worker thread management was migrated to managed code and uses Environment.ProcessorCount, so after Update Environment.ProcessorCount on Windows to take into account the processor affinity mask #45943 thread pool worker thread parallelization in .NET 6 would be the same as before in that scenario.

Metadata

Metadata

Assignees

Labels

area-System.Threadingbreaking-changeIssue or PR that represents a breaking API or functional change over a prerelease.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions