Skip to content

Commit

Permalink
Use logical core count (#6014)
Browse files Browse the repository at this point in the history
Crossplatform version of GetLogicalCoreCount

Move logic from XMake into NativeMethodsShared
  • Loading branch information
cdmihai committed Jan 15, 2021
1 parent ab9a839 commit 34bbbed
Show file tree
Hide file tree
Showing 15 changed files with 52 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/Build.OM.UnitTests/Definition/Project_Tests.cs
Expand Up @@ -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 =
Expand Down
Expand Up @@ -11,6 +11,8 @@

<AssemblyName>Microsoft.Build.Engine.OM.UnitTests</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

<DefineConstants>$(DefineConstants);MICROSOFT_BUILD_ENGINE_OM_UNITTESTS</DefineConstants>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
Expand Up @@ -3986,7 +3986,7 @@ public void MultiProcReentrantProjectWithCallTargetDoesNotFail()
var buildParameters = new BuildParameters()
{
DisableInProcNode = true,
MaxNodeCount = Environment.ProcessorCount,
MaxNodeCount = NativeMethodsShared.GetLogicalCoreCount(),
EnableNodeReuse = false,
Loggers = new List<ILogger>()
{
Expand Down
9 changes: 5 additions & 4 deletions 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;
Expand Down Expand Up @@ -61,7 +62,7 @@ public void GivenExceptionsOnWorkerThread_CompletesAndThrowsExceptions()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
DegreeOfParallelism = Environment.ProcessorCount,
DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount(),
WorkItemsToAdd = new List<WorkItem>
{
new WorkItem
Expand Down Expand Up @@ -89,7 +90,7 @@ public void GivenNoWorkItemAndMultipleWorkers_Completes()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
DegreeOfParallelism = Environment.ProcessorCount
DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount()
});
}

Expand All @@ -104,7 +105,7 @@ public void GivenRecursiveWorkItemsAndMultipleWorkers_Completes()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
DegreeOfParallelism = Environment.ProcessorCount,
DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount(),
WorkItemsToAdd = new List<WorkItem>
{
new WorkItem
Expand Down Expand Up @@ -168,7 +169,7 @@ public void GivenWorkItemsAndMultipleWorkers_Completes()
{
TestParallelWorkSet(new ParallelWorkSetTestCase
{
DegreeOfParallelism = Environment.ProcessorCount,
DegreeOfParallelism = NativeMethodsShared.GetLogicalCoreCount(),
WorkItemsToAdd = new List<WorkItem>
{
new WorkItem
Expand Down
2 changes: 1 addition & 1 deletion src/Build/BackEnd/Components/Caching/ResultsCache.cs
Expand Up @@ -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<int, BuildResult>(Environment.ProcessorCount, capacity));
capacity => new ConcurrentDictionary<int, BuildResult>(NativeMethodsShared.GetLogicalCoreCount(), capacity));

if (translator.Mode == TranslationDirection.ReadFromStream)
{
Expand Down
Expand Up @@ -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<SdkResolver, object> resolverState = _resolverStateBySubmission.GetOrAdd(submissionId, new ConcurrentDictionary<SdkResolver, object>(Environment.ProcessorCount, _resolvers.Count));
ConcurrentDictionary<SdkResolver, object> resolverState = _resolverStateBySubmission.GetOrAdd(submissionId, new ConcurrentDictionary<SdkResolver, object>(NativeMethodsShared.GetLogicalCoreCount(), _resolvers.Count));

resolverState.AddOrUpdate(resolver, state, (sdkResolver, obj) => state);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Build/Graph/ProjectGraph.cs
Expand Up @@ -330,7 +330,7 @@ public ProjectGraph(ProjectGraphEntryPoint entryPoint, ProjectCollection project
entryPoints,
projectCollection,
projectInstanceFactory,
Environment.ProcessorCount,
NativeMethodsShared.GetLogicalCoreCount(),
CancellationToken.None)
{
}
Expand Down Expand Up @@ -371,7 +371,7 @@ public ProjectGraph(ProjectGraphEntryPoint entryPoint, ProjectCollection project
entryPoints,
projectCollection,
projectInstanceFactory,
Environment.ProcessorCount,
NativeMethodsShared.GetLogicalCoreCount(),
cancellationToken)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/MSBuild.UnitTests/XMake_Tests.cs
Expand Up @@ -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();
Expand Down
19 changes: 1 addition & 18 deletions src/MSBuild/XMake.cs
Expand Up @@ -1702,24 +1702,7 @@ internal static void GatherCommandLineSwitches(List<string> 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) ||
Expand Down
2 changes: 1 addition & 1 deletion src/MSBuildTaskHost/Concurrent/ConcurrentDictionary.cs
Expand Up @@ -93,7 +93,7 @@ private static bool IsValueWriteAtomic()
/// </summary>
public ConcurrentDictionary(IEqualityComparer<TKey> 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
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/FileMatcher.cs
Expand Up @@ -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,
Expand Down
31 changes: 30 additions & 1 deletion src/Shared/NativeMethodsShared.cs
Expand Up @@ -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;
}

/// <summary>
/// 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
/// </summary>
public unsafe static int GetLogicalCoreCount()
private unsafe static int GetLogicalCoreCountOnWindows()
{
uint len = 0;
const int ERROR_INSUFFICIENT_BUFFER = 122;
Expand Down
4 changes: 2 additions & 2 deletions src/Shared/OpportunisticIntern.cs
Expand Up @@ -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<string, string> _internedStrings = new ConcurrentDictionary<string, string>(Environment.ProcessorCount, InitialCapacity, StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, string> _internedStrings = new ConcurrentDictionary<string, string>(NativeMethodsShared.GetLogicalCoreCount(), InitialCapacity, StringComparer.Ordinal);
#endif

#region Statistics
Expand Down
3 changes: 2 additions & 1 deletion src/Shared/WeakStringCache.Concurrent.cs
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.Build.Shared;

namespace Microsoft.Build
{
Expand All @@ -17,7 +18,7 @@ internal sealed partial class WeakStringCache : IDisposable

public WeakStringCache()
{
_stringsByHashCode = new ConcurrentDictionary<int, StringWeakHandle>(Environment.ProcessorCount, _initialCapacity);
_stringsByHashCode = new ConcurrentDictionary<int, StringWeakHandle>(NativeMethodsShared.GetLogicalCoreCount(), _initialCapacity);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Tasks/Copy.cs
Expand Up @@ -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;

/// <summary>
/// Constructor.
Expand Down

0 comments on commit 34bbbed

Please sign in to comment.