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.