Skip to content

Commit

Permalink
Copy GC settings from host process (#1765)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsitnik committed Aug 4, 2021
1 parent 8cb701c commit 8f81b5b
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 46 deletions.
24 changes: 13 additions & 11 deletions src/BenchmarkDotNet/Extensions/ProcessExtensions.cs
Expand Up @@ -124,7 +124,7 @@ internal static void SetEnvironmentVariables(this ProcessStartInfo start, Benchm
// we have to set "COMPlus_GC*" environment variables as documented in
// https://docs.microsoft.com/en-us/dotnet/core/run-time-config/garbage-collector
if (benchmarkCase.Job.Infrastructure.Toolchain is CoreRunToolchain _)
start.SetCoreRunEnvironmentVariables(benchmarkCase);
start.SetCoreRunEnvironmentVariables(benchmarkCase, resolver);

if (!benchmarkCase.Job.HasValue(EnvironmentMode.EnvironmentVariablesCharacteristic))
return;
Expand Down Expand Up @@ -229,19 +229,21 @@ private static int RunProcessAndIgnoreOutput(string fileName, string arguments,
}
}

private static void SetCoreRunEnvironmentVariables(this ProcessStartInfo start, BenchmarkCase benchmarkCase)
private static void SetCoreRunEnvironmentVariables(this ProcessStartInfo start, BenchmarkCase benchmarkCase, IResolver resolver)
{
var gcMode = benchmarkCase.Job.Environment.Gc;
if (!gcMode.HasChanges)
return; // do nothing for the default settings

start.EnvironmentVariables["COMPlus_gcServer"] = gcMode.Server ? "1" : "0";
start.EnvironmentVariables["COMPlus_gcConcurrent"] = gcMode.Concurrent ? "1" : "0";
start.EnvironmentVariables["COMPlus_GCCpuGroup"] = gcMode.CpuGroups ? "1" : "0";
start.EnvironmentVariables["COMPlus_gcAllowVeryLargeObjects"] = gcMode.AllowVeryLargeObjects ? "1" : "0";
start.EnvironmentVariables["COMPlus_GCRetainVM"] = gcMode.RetainVm ? "1" : "0";
start.EnvironmentVariables["COMPlus_GCNoAffinitize"] = gcMode.NoAffinitize ? "1" : "0";

start.EnvironmentVariables["COMPlus_gcServer"] = gcMode.ResolveValue(GcMode.ServerCharacteristic, resolver) ? "1" : "0";
start.EnvironmentVariables["COMPlus_gcConcurrent"] = gcMode.ResolveValue(GcMode.ConcurrentCharacteristic, resolver) ? "1" : "0";

if (gcMode.HasValue(GcMode.CpuGroupsCharacteristic))
start.EnvironmentVariables["COMPlus_GCCpuGroup"] = gcMode.ResolveValue(GcMode.CpuGroupsCharacteristic, resolver) ? "1" : "0";
if (gcMode.HasValue(GcMode.AllowVeryLargeObjectsCharacteristic))
start.EnvironmentVariables["COMPlus_gcAllowVeryLargeObjects"] = gcMode.ResolveValue(GcMode.AllowVeryLargeObjectsCharacteristic, resolver) ? "1" : "0";
if (gcMode.HasValue(GcMode.RetainVmCharacteristic))
start.EnvironmentVariables["COMPlus_GCRetainVM"] = gcMode.ResolveValue(GcMode.RetainVmCharacteristic, resolver) ? "1" : "0";
if (gcMode.HasValue(GcMode.NoAffinitizeCharacteristic))
start.EnvironmentVariables["COMPlus_GCNoAffinitize"] = gcMode.ResolveValue(GcMode.NoAffinitizeCharacteristic, resolver) ? "1" : "0";
if (gcMode.HasValue(GcMode.HeapAffinitizeMaskCharacteristic))
start.EnvironmentVariables["COMPlus_GCHeapAffinitizeMask"] = gcMode.HeapAffinitizeMask.ToString("X");
if (gcMode.HasValue(GcMode.HeapCountCharacteristic))
Expand Down
12 changes: 6 additions & 6 deletions src/BenchmarkDotNet/Toolchains/AppConfigGenerator.cs
Expand Up @@ -100,15 +100,15 @@ private static void GenerateJitSettings(XmlDocument xmlDocument, XmlNode runtime

private static void GenerateGCSettings(XmlDocument xmlDocument, XmlNode runtimeElement, GcMode gcMode, IResolver resolver)
{
if (!gcMode.HasChanges)
return;

CreateNodeWithAttribute(xmlDocument, runtimeElement, "gcConcurrent", "enabled", gcMode.ResolveValue(GcMode.ConcurrentCharacteristic, resolver).ToLowerCase());
CreateNodeWithAttribute(xmlDocument, runtimeElement, "gcServer", "enabled", gcMode.ResolveValue(GcMode.ServerCharacteristic, resolver).ToLowerCase());
CreateNodeWithAttribute(xmlDocument, runtimeElement, "GCCpuGroup", "enabled", gcMode.ResolveValue(GcMode.CpuGroupsCharacteristic, resolver).ToLowerCase());
CreateNodeWithAttribute(xmlDocument, runtimeElement, "gcAllowVeryLargeObjects", "enabled", gcMode.ResolveValue(GcMode.AllowVeryLargeObjectsCharacteristic, resolver).ToLowerCase());
CreateNodeWithAttribute(xmlDocument, runtimeElement, "GCNoAffinitize", "enabled", gcMode.ResolveValue(GcMode.NoAffinitizeCharacteristic, resolver).ToLowerCase());

if (gcMode.HasValue(GcMode.CpuGroupsCharacteristic))
CreateNodeWithAttribute(xmlDocument, runtimeElement, "GCCpuGroup", "enabled", gcMode.ResolveValue(GcMode.CpuGroupsCharacteristic, resolver).ToLowerCase());
if (gcMode.HasValue(GcMode.AllowVeryLargeObjectsCharacteristic))
CreateNodeWithAttribute(xmlDocument, runtimeElement, "gcAllowVeryLargeObjects", "enabled", gcMode.ResolveValue(GcMode.AllowVeryLargeObjectsCharacteristic, resolver).ToLowerCase());
if (gcMode.HasValue(GcMode.NoAffinitizeCharacteristic))
CreateNodeWithAttribute(xmlDocument, runtimeElement, "GCNoAffinitize", "enabled", gcMode.ResolveValue(GcMode.NoAffinitizeCharacteristic, resolver).ToLowerCase());
if (gcMode.HasValue(GcMode.HeapAffinitizeMaskCharacteristic))
CreateNodeWithAttribute(xmlDocument, runtimeElement, "GCHeapAffinitizeMask", "enabled", gcMode.ResolveValue(GcMode.HeapAffinitizeMaskCharacteristic, resolver).ToString());
if (gcMode.HasValue(GcMode.HeapCountCharacteristic))
Expand Down
17 changes: 8 additions & 9 deletions src/BenchmarkDotNet/Toolchains/CsProj/CsProjGenerator.cs
Expand Up @@ -83,16 +83,15 @@ protected override void GenerateProject(BuildPartition buildPartition, Artifacts
[PublicAPI]
protected virtual string GetRuntimeSettings(GcMode gcMode, IResolver resolver)
{
if (!gcMode.HasChanges)
return string.Empty;

return new StringBuilder(80)
var builder = new StringBuilder(80)
.AppendLine("<PropertyGroup>")
.AppendLine($"<ServerGarbageCollection>{gcMode.ResolveValue(GcMode.ServerCharacteristic, resolver).ToLowerCase()}</ServerGarbageCollection>")
.AppendLine($"<ConcurrentGarbageCollection>{gcMode.ResolveValue(GcMode.ConcurrentCharacteristic, resolver).ToLowerCase()}</ConcurrentGarbageCollection>")
.AppendLine($"<RetainVMGarbageCollection>{gcMode.ResolveValue(GcMode.RetainVmCharacteristic, resolver).ToLowerCase()}</RetainVMGarbageCollection>")
.AppendLine("</PropertyGroup>")
.ToString();
.AppendLine($"<ServerGarbageCollection>{gcMode.ResolveValue(GcMode.ServerCharacteristic, resolver).ToLowerCase()}</ServerGarbageCollection>")
.AppendLine($"<ConcurrentGarbageCollection>{gcMode.ResolveValue(GcMode.ConcurrentCharacteristic, resolver).ToLowerCase()}</ConcurrentGarbageCollection>");

if (gcMode.HasValue(GcMode.RetainVmCharacteristic))
builder.AppendLine($"<RetainVMGarbageCollection>{gcMode.ResolveValue(GcMode.RetainVmCharacteristic, resolver).ToLowerCase()}</RetainVMGarbageCollection>");

return builder.AppendLine("</PropertyGroup>").ToString();
}

// the host project or one of the .props file that it imports might contain some custom settings that needs to be copied, sth like
Expand Down
Expand Up @@ -12,6 +12,7 @@
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
<NoWarn>$(NoWarn);CA2007</NoWarn>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<Content Include="xunit.runner.json">
Expand Down
8 changes: 6 additions & 2 deletions tests/BenchmarkDotNet.IntegrationTests/GcModeTests.cs
Expand Up @@ -18,10 +18,14 @@ public class GcModeTests : BenchmarkTestExecutor
private IConfig CreateConfig(GcMode gc) => ManualConfig.CreateEmpty().AddJob(new Job(Job.Dry, gc));

[Fact]
public void CanHostGcMode()
public void HostProcessSettingsAreCopiedByDefault()
{
var config = CreateConfig(GcMode.Default);
CanExecute<WorkstationGcOnly>(config);

if (GCSettings.IsServerGC)
CanExecute<ServerModeEnabled>(config);
else
CanExecute<WorkstationGcOnly>(config);
}

[Fact]
Expand Down
60 changes: 42 additions & 18 deletions tests/BenchmarkDotNet.Tests/AppConfigGeneratorTests.cs
Expand Up @@ -5,9 +5,11 @@
using System.Text;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Running;
using Xunit;
using BenchmarkDotNet.Tests.XUnit;
using System.Runtime;

namespace BenchmarkDotNet.Tests
{
Expand All @@ -20,10 +22,10 @@ public void GeneratesMinimalRequiredAppConfigForEmptySource()
{
using (var destination = new Utf8StringWriter())
{
const string expectedMinimal =
string expectedMinimal =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<configuration>" +
"<runtime/>" +
$"<runtime>{GcSettings}</runtime>" +
"</configuration>";

AppConfigGenerator.Generate(Job.Default, TextReader.Null, destination, Resolver);
Expand All @@ -38,10 +40,10 @@ public void GeneratesMinimalRequiredAppConfigForAlmostEmptySource()
using (var source = new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))
using (var destination = new Utf8StringWriter())
{
const string expectedMinimal =
string expectedMinimal =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<configuration>" +
"<runtime/>" +
$"<runtime>{GcSettings}</runtime>" +
"</configuration>";

AppConfigGenerator.Generate(Job.Default, source, destination, Resolver);
Expand All @@ -53,50 +55,69 @@ public void GeneratesMinimalRequiredAppConfigForAlmostEmptySource()
[Fact]
public void RewritesCustomSettings()
{
const string customSettings =
string customSettingsWithoutRuntimeNode =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<!--" +
"commentsAreSupported" +
"-->" +
"<configuration>" +
"<someConfig>withItsValue</someConfig>" +
"<runtime/>" +
"</configuration>";

using (var source = new StringReader(customSettings))
string customSettingsWithRuntimeNode =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<!--" +
"commentsAreSupported" +
"-->" +
"<configuration>" +
"<someConfig>withItsValue</someConfig>" +
$"<runtime>{GcSettings}</runtime>" +
"</configuration>";

using (var source = new StringReader(customSettingsWithoutRuntimeNode))
using (var destination = new Utf8StringWriter())
{
AppConfigGenerator.Generate(Job.Default, source, destination, Resolver);

AssertAreEqualIgnoringWhitespacesAndCase(customSettings, destination.ToString());
AssertAreEqualIgnoringWhitespacesAndCase(customSettingsWithRuntimeNode, destination.ToString());
}
}

[Fact]
public void RewritesCustomRuntimeSettings()
{
const string customSettings =
string customSettingsBefore =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<!--" +
"commentsAreSupported" +
"-->" +
"<configuration>" +
"<someConfig>withItsValue</someConfig>" +
"<runtime><AppContextSwitchOverrides value=\"Switch.System.IO.UseLegacyPathHandling=false\"/></runtime>" +
$"<runtime><AppContextSwitchOverrides value=\"Switch.System.IO.UseLegacyPathHandling=false\"/></runtime>" +
"</configuration>";

using (var source = new StringReader(customSettings))
string customSettingsAfter =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<!--" +
"commentsAreSupported" +
"-->" +
"<configuration>" +
"<someConfig>withItsValue</someConfig>" +
$"<runtime><AppContextSwitchOverrides value=\"Switch.System.IO.UseLegacyPathHandling=false\"/>{GcSettings}</runtime>" +
"</configuration>";

using (var source = new StringReader(customSettingsBefore))
using (var destination = new Utf8StringWriter())
{
AppConfigGenerator.Generate(Job.Default, source, destination, Resolver);

AssertAreEqualIgnoringWhitespacesAndCase(customSettings, destination.ToString());
AssertAreEqualIgnoringWhitespacesAndCase(customSettingsAfter, destination.ToString());
}
}

[Theory]
[InlineData(Jit.LegacyJit, "<runtime><useLegacyJit enabled=\"1\" /></runtime>")]
[InlineData(Jit.RyuJit, "<runtime><useLegacyJit enabled=\"0\" /></runtime>")]
[InlineData(Jit.LegacyJit, "<useLegacyJit enabled=\"1\" />")]
[InlineData(Jit.RyuJit, "<useLegacyJit enabled=\"0\" />")]
public void GeneratesRightJitSettings(Jit jit, string expectedRuntimeNode)
{
const string customSettings =
Expand All @@ -109,7 +130,7 @@ public void GeneratesRightJitSettings(Jit jit, string expectedRuntimeNode)
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<configuration>" +
"<someConfig>withItsValue</someConfig>" +
expectedRuntimeNode +
$"<runtime>{expectedRuntimeNode}{GcSettings}</runtime>" +
"</configuration>" + Environment.NewLine;

using (var source = new StringReader(customSettings))
Expand All @@ -133,7 +154,7 @@ public void RemovesStartupSettingsForPrivateBuildsOfClr()
string withoutStartup =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<configuration>" +
"<runtime/>" +
$"<runtime>{GcSettings}</runtime>" +
"</configuration>" + Environment.NewLine;

using (var source = new StringReader(input))
Expand All @@ -158,7 +179,7 @@ public void LeavsStartupSettingsIntactForNonPrivateBuildsOfClr()
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<configuration>" +
"<startup><supportedRuntime version=\"v4.0\" sku=\".NETFramework,Version=v4.6.1\" /></startup>" +
"<runtime/>" +
$"<runtime>{GcSettings}</runtime>" +
"</configuration>" + Environment.NewLine;

using (var source = new StringReader(input))
Expand Down Expand Up @@ -186,7 +207,7 @@ public void RewritesCustomAssemblyBindingRedirects()
"</runtime>" +
"</configuration>";

const string settingsWithBindingsAndJit =
string settingsWithBindingsAndJit =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<configuration>" +
"<runtime>" +
Expand All @@ -197,6 +218,7 @@ public void RewritesCustomAssemblyBindingRedirects()
"</dependentAssembly>" +
"</assemblyBinding>" +
"<useLegacyJit enabled =\"0\" />" +
GcSettings +
"</runtime>" +
"</configuration>";

Expand Down Expand Up @@ -234,6 +256,8 @@ private static string RemoveWhiteSpaces(string input)
}
return buffer.ToString();
}

private static readonly string GcSettings = $"<gcConcurrentenabled=\"{(GCSettings.LatencyMode != GCLatencyMode.Batch).ToLowerCase()}\"/><gcServerenabled=\"{GCSettings.IsServerGC.ToLowerCase()}\"/>";
}

internal class Utf8StringWriter : StringWriter
Expand Down

0 comments on commit 8f81b5b

Please sign in to comment.