diff --git a/src/Build.OM.UnitTests/Definition/Project_Tests.cs b/src/Build.OM.UnitTests/Definition/Project_Tests.cs
index 9474574afd5..6dcaa45e84e 100644
--- a/src/Build.OM.UnitTests/Definition/Project_Tests.cs
+++ b/src/Build.OM.UnitTests/Definition/Project_Tests.cs
@@ -2850,7 +2850,7 @@ public void GetItemProvenanceGlobMatchesItselfAsGlob()
[Fact]
public void GetItemProvenanceResultsShouldBeInItemElementOrder()
{
- var itemElements = Environment.ProcessorCount * 5;
+ var itemElements = NativeMethodsShared.GetLogicalCoreCount() * 5;
var expected = new ProvenanceResultTupleList();
var project =
diff --git a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj
index 1f887d229ee..5ec78a92068 100644
--- a/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj
+++ b/src/Build.OM.UnitTests/Microsoft.Build.Engine.OM.UnitTests.csproj
@@ -11,6 +11,8 @@
Microsoft.Build.Engine.OM.UnitTests
true
+
+ $(DefineConstants);MICROSOFT_BUILD_ENGINE_OM_UNITTESTS
diff --git a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
index b2936c5c111..3135035b20b 100644
--- a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
@@ -3986,7 +3986,7 @@ public void MultiProcReentrantProjectWithCallTargetDoesNotFail()
var buildParameters = new BuildParameters()
{
DisableInProcNode = true,
- MaxNodeCount = Environment.ProcessorCount,
+ MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(),
EnableNodeReuse = false,
Loggers = new List()
{
diff --git a/src/Build.UnitTests/Graph/ParallelWorkSet_Tests.cs b/src/Build.UnitTests/Graph/ParallelWorkSet_Tests.cs
index 234f6f3a47d..5592f324956 100644
--- a/src/Build.UnitTests/Graph/ParallelWorkSet_Tests.cs
+++ b/src/Build.UnitTests/Graph/ParallelWorkSet_Tests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
+using Microsoft.Build.Shared;
using Microsoft.Build.UnitTests;
using Shouldly;
using Xunit;
@@ -61,7 +62,7 @@ public void GivenExceptionsOnWorkerThread_CompletesAndThrowsExceptions()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
- DegreeOfParallelism = Environment.ProcessorCount,
+ DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount(),
WorkItemsToAdd = new List
{
new WorkItem
@@ -89,7 +90,7 @@ public void GivenNoWorkItemAndMultipleWorkers_Completes()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
- DegreeOfParallelism = Environment.ProcessorCount
+ DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount()
});
}
@@ -104,7 +105,7 @@ public void GivenRecursiveWorkItemsAndMultipleWorkers_Completes()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
- DegreeOfParallelism = Environment.ProcessorCount,
+ DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount(),
WorkItemsToAdd = new List
{
new WorkItem
@@ -168,7 +169,7 @@ public void GivenWorkItemsAndMultipleWorkers_Completes()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
- DegreeOfParallelism = Environment.ProcessorCount,
+ DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount(),
WorkItemsToAdd = new List
{
new WorkItem
diff --git a/src/Build/BackEnd/Components/Caching/ResultsCache.cs b/src/Build/BackEnd/Components/Caching/ResultsCache.cs
index a59c7f60914..0cf24b8aa14 100644
--- a/src/Build/BackEnd/Components/Caching/ResultsCache.cs
+++ b/src/Build/BackEnd/Components/Caching/ResultsCache.cs
@@ -240,7 +240,7 @@ public void Translate(ITranslator translator)
ref localReference,
(ITranslator aTranslator, ref int i) => aTranslator.Translate(ref i),
(ITranslator aTranslator, ref BuildResult result) => aTranslator.Translate(ref result),
- capacity => new ConcurrentDictionary(Environment.ProcessorCount, capacity));
+ capacity => new ConcurrentDictionary(NativeMethodsShared.GetLogicalCoreCount(), capacity));
if (translator.Mode == TranslationDirection.ReadFromStream)
{
diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs
index 1ca540e9788..237ea72d01e 100644
--- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs
+++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs
@@ -243,7 +243,7 @@ private void SetResolverState(int submissionId, SdkResolver resolver, object sta
// Do not set state for resolution requests that are not associated with a valid build submission ID
if (submissionId != BuildEventContext.InvalidSubmissionId)
{
- ConcurrentDictionary resolverState = _resolverStateBySubmission.GetOrAdd(submissionId, new ConcurrentDictionary(Environment.ProcessorCount, _resolvers.Count));
+ ConcurrentDictionary resolverState = _resolverStateBySubmission.GetOrAdd(submissionId, new ConcurrentDictionary(NativeMethodsShared.GetLogicalCoreCount(), _resolvers.Count));
resolverState.AddOrUpdate(resolver, state, (sdkResolver, obj) => state);
}
diff --git a/src/Build/Graph/ProjectGraph.cs b/src/Build/Graph/ProjectGraph.cs
index 74939362600..b445f72d83d 100644
--- a/src/Build/Graph/ProjectGraph.cs
+++ b/src/Build/Graph/ProjectGraph.cs
@@ -330,7 +330,7 @@ public ProjectGraph(ProjectGraphEntryPoint entryPoint, ProjectCollection project
entryPoints,
projectCollection,
projectInstanceFactory,
- Environment.ProcessorCount,
+ NativeMethodsShared.GetLogicalCoreCount(),
CancellationToken.None)
{
}
@@ -371,7 +371,7 @@ public ProjectGraph(ProjectGraphEntryPoint entryPoint, ProjectCollection project
entryPoints,
projectCollection,
projectInstanceFactory,
- Environment.ProcessorCount,
+ NativeMethodsShared.GetLogicalCoreCount(),
cancellationToken)
{
}
diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs
index 84f960fd86f..630890e9085 100644
--- a/src/MSBuild.UnitTests/XMake_Tests.cs
+++ b/src/MSBuild.UnitTests/XMake_Tests.cs
@@ -82,7 +82,7 @@ public void GatherCommandLineSwitchesMaxCpuCountWithoutArgument()
MSBuildApp.GatherCommandLineSwitches(arguments, switches);
string[] parameters = switches[CommandLineSwitches.ParameterizedSwitch.MaxCPUCount];
- parameters[1].ShouldBe(Convert.ToString(Environment.ProcessorCount));
+ parameters[1].ShouldBe(Convert.ToString(NativeMethodsShared.GetLogicalCoreCount()));
parameters.Length.ShouldBe(2);
switches.HaveErrors().ShouldBeFalse();
diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs
index 2f8fceebe0e..89b5641c4c4 100644
--- a/src/MSBuild/XMake.cs
+++ b/src/MSBuild/XMake.cs
@@ -1702,24 +1702,7 @@ internal static void GatherCommandLineSwitches(List commandLineArgs, Com
if (string.Equals(switchName, "m", StringComparison.OrdinalIgnoreCase) ||
string.Equals(switchName, "maxcpucount", StringComparison.OrdinalIgnoreCase))
{
- int numberOfCpus = Environment.ProcessorCount;
-#if !MONO
- // .NET Core on Windows returns a core count limited to the current NUMA node
- // https://github.com/dotnet/runtime/issues/29686
- // so always double-check it.
- if (NativeMethodsShared.IsWindows && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_8)
-#if NETFRAMEWORK
- // .NET Framework calls Windows APIs that have a core count limit (32/64 depending on process bitness).
- // So if we get a high core count on full framework, double-check it.
- && (numberOfCpus >= 32)
-#endif
- )
- {
- var result = NativeMethodsShared.GetLogicalCoreCount();
- if(result != -1)
- numberOfCpus = result;
- }
-#endif
+ int numberOfCpus = NativeMethodsShared.GetLogicalCoreCount();
switchParameters = $":{numberOfCpus}";
}
else if (string.Equals(switchName, "bl", StringComparison.OrdinalIgnoreCase) ||
diff --git a/src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs b/src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs
index e83adf88dce..76a126450a7 100644
--- a/src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs
+++ b/src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs
@@ -93,7 +93,7 @@ private static bool IsValueWriteAtomic()
///
public ConcurrentDictionary(IEqualityComparer comparer = null)
{
- int concurrencyLevel = Environment.ProcessorCount;
+ int concurrencyLevel = NativeMethodsShared.GetLogicalCoreCount();
int capacity = DefaultCapacity;
// The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs
index a9c8fa2b2f3..fb276f722ef 100644
--- a/src/Shared/FileMatcher.cs
+++ b/src/Shared/FileMatcher.cs
@@ -2390,7 +2390,7 @@ static string[] CreateArrayWithSingleItemIfNotExcluded(string filespecUnescaped,
// Set to use only half processors when we have 4 or more of them, in order to not be too aggresive
// By setting MaxTasksPerIteration to the maximum amount of tasks, which means that only one
// Parallel.ForEach will run at once, we get a stable number of threads being created.
- var maxTasks = Math.Max(1, Environment.ProcessorCount / 2);
+ var maxTasks = Math.Max(1, NativeMethodsShared.GetLogicalCoreCount() / 2);
var taskOptions = new TaskOptions(maxTasks)
{
AvailableTasks = maxTasks,
diff --git a/src/Shared/NativeMethodsShared.cs b/src/Shared/NativeMethodsShared.cs
index 7d66b7731ce..4818d7eda9c 100644
--- a/src/Shared/NativeMethodsShared.cs
+++ b/src/Shared/NativeMethodsShared.cs
@@ -508,13 +508,42 @@ public SystemInformationData()
}
}
+ public static int GetLogicalCoreCount()
+ {
+ int numberOfCpus = Environment.ProcessorCount;
+#if !MONO
+ // .NET Core on Windows returns a core count limited to the current NUMA node
+ // https://github.com/dotnet/runtime/issues/29686
+ // so always double-check it.
+ if (IsWindows
+#if !CLR2COMPATIBILITY && !MICROSOFT_BUILD_ENGINE_OM_UNITTESTS
+ && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_8)
+#endif
+#if NETFRAMEWORK
+ // .NET Framework calls Windows APIs that have a core count limit (32/64 depending on process bitness).
+ // So if we get a high core count on full framework, double-check it.
+ && (numberOfCpus >= 32)
+#endif
+ )
+ {
+ var result = GetLogicalCoreCountOnWindows();
+ if (result != -1)
+ {
+ numberOfCpus = result;
+ }
+ }
+#endif
+
+ return numberOfCpus;
+ }
+
///
/// Get the exact physical core count on Windows
/// Useful for getting the exact core count in 32 bits processes,
/// as Environment.ProcessorCount has a 32-core limit in that case.
/// https://github.com/dotnet/runtime/blob/221ad5b728f93489655df290c1ea52956ad8f51c/src/libraries/System.Runtime.Extensions/src/System/Environment.Windows.cs#L171-L210
///
- public unsafe static int GetLogicalCoreCount()
+ private unsafe static int GetLogicalCoreCountOnWindows()
{
uint len = 0;
const int ERROR_INSUFFICIENT_BUFFER = 122;
diff --git a/src/Shared/OpportunisticIntern.cs b/src/Shared/OpportunisticIntern.cs
index 6da9e42a34c..48d5d407c7c 100644
--- a/src/Shared/OpportunisticIntern.cs
+++ b/src/Shared/OpportunisticIntern.cs
@@ -614,9 +614,9 @@ private class BucketedPrioritizedStringList : IInternerImplementation
// ConcurrentDictionary starts with capacity 31 but we're usually adding far more than that. Make a better first capacity guess to reduce
// ConcurrentDictionary having to take all internal locks to upgrade its bucket list. Note that the number should be prime per the
// comments on the code at https://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,122
- // Also note default lock count is Environment.ProcessorCount from the same code.
+ // Also note default lock count is NativeMethodsShared.GetLogicalCoreCount() from the same code.
private const int InitialCapacity = 2053;
- private readonly ConcurrentDictionary _internedStrings = new ConcurrentDictionary(Environment.ProcessorCount, InitialCapacity, StringComparer.Ordinal);
+ private readonly ConcurrentDictionary _internedStrings = new ConcurrentDictionary(NativeMethodsShared.GetLogicalCoreCount(), InitialCapacity, StringComparer.Ordinal);
#endif
#region Statistics
diff --git a/src/Shared/WeakStringCache.Concurrent.cs b/src/Shared/WeakStringCache.Concurrent.cs
index bd30282b614..318aeafc131 100644
--- a/src/Shared/WeakStringCache.Concurrent.cs
+++ b/src/Shared/WeakStringCache.Concurrent.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using Microsoft.Build.Shared;
namespace Microsoft.Build
{
@@ -17,7 +18,7 @@ internal sealed partial class WeakStringCache : IDisposable
public WeakStringCache()
{
- _stringsByHashCode = new ConcurrentDictionary(Environment.ProcessorCount, _initialCapacity);
+ _stringsByHashCode = new ConcurrentDictionary(NativeMethodsShared.GetLogicalCoreCount(), _initialCapacity);
}
///
diff --git a/src/Tasks/Copy.cs b/src/Tasks/Copy.cs
index 013176c94be..67d779798e7 100644
--- a/src/Tasks/Copy.cs
+++ b/src/Tasks/Copy.cs
@@ -39,7 +39,7 @@ public class Copy : TaskExtension, ICancelableTask
// threads at the advantage of performing file copies more quickly in the kernel - we must avoid
// taking up the whole threadpool esp. when hosted in Visual Studio. IOW we use a specific number
// instead of int.MaxValue.
- private static readonly int DefaultCopyParallelism = Environment.ProcessorCount > 4 ? 6 : 4;
+ private static readonly int DefaultCopyParallelism = NativeMethodsShared.GetLogicalCoreCount() > 4 ? 6 : 4;
///
/// Constructor.