Memory usage improvements in build (#2626)

* Use a WorkspaceContext in dotnet-build to cache project data across
multiple compilations in a single build action
* Dramatically reduce string and object duplication by introducing a
"Symbol Table" that shares instances of NuGetVersion, NuGetFramework,
VersionRange and string across multiple lock-file parses

Test Results:
* Testing was done by compiling Microsoft.AspNetCore.Mvc (and it's
dependencies) and taking memory snapshots after each compilation in
dotMemory
* We used to allocate ~3MB and deallocate ~2.5MB on EACH compilation in
a single build action. This has been reduced to ~120KB
allocated/deallocated
* After introducing WorkspaceContext, total memory usage spiked from 6MB
across the whole build action to about 13MB, introducing the symbol
table dropped it back to about 5-6MB.
This commit is contained in:
Andrew Stanton-Nurse 2016-04-22 15:01:56 -07:00
parent 80df3688b1
commit ef0ca39da1
42 changed files with 496 additions and 268 deletions

View file

@ -43,14 +43,14 @@ namespace Microsoft.DotNet.ProjectModel.Tests
}
[Theory]
[InlineDataAttribute("TestMscorlibReference", true)]
[InlineDataAttribute("TestMscorlibReference", false)]
[InlineDataAttribute("TestMicrosoftCSharpReference", true)]
[InlineDataAttribute("TestMicrosoftCSharpReference", false)]
[InlineDataAttribute("TestSystemReference", true)]
[InlineDataAttribute("TestSystemReference", false)]
[InlineDataAttribute("TestSystemCoreReference", true)]
[InlineDataAttribute("TestSystemCoreReference", false)]
[InlineData("TestMscorlibReference", true)]
[InlineData("TestMscorlibReference", false)]
[InlineData("TestMicrosoftCSharpReference", true)]
[InlineData("TestMicrosoftCSharpReference", false)]
[InlineData("TestSystemReference", true)]
[InlineData("TestSystemReference", false)]
[InlineData("TestSystemCoreReference", true)]
[InlineData("TestSystemCoreReference", false)]
public void TestDuplicateDefaultDesktopReferences(string sampleName, bool withLockFile)
{
var instance = TestAssetsManager.CreateTestInstance(sampleName);
@ -65,7 +65,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests
Assert.Equal(4, context.RootProject.Dependencies.Count());
}
[Fact]
public void NoDuplicateReferencesWhenFrameworkMissing()
{
@ -79,7 +79,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests
// Will fail with dupes if any
context.LibraryManager.GetLibraries().ToDictionary(l => l.Identity.Name);
}
[Fact]
public void NetCore50ShouldNotResolveFrameworkAssemblies()
{

View file

@ -109,12 +109,11 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
private CommandResult RunProcess(string executable, string args, StreamForwarder stdOut, StreamForwarder stdErr)
{
CurrentProcess = StartProcess(executable, args);
var threadOut = stdOut.BeginRead(CurrentProcess.StandardOutput);
var threadErr = stdErr.BeginRead(CurrentProcess.StandardError);
var taskOut = stdOut.BeginRead(CurrentProcess.StandardOutput);
var taskErr = stdErr.BeginRead(CurrentProcess.StandardError);
CurrentProcess.WaitForExit();
threadOut.Join();
threadErr.Join();
Task.WaitAll(taskOut, taskErr);
var result = new CommandResult(
CurrentProcess.StartInfo,
@ -128,14 +127,13 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
private Task<CommandResult> RunProcessAsync(string executable, string args, StreamForwarder stdOut, StreamForwarder stdErr)
{
CurrentProcess = StartProcess(executable, args);
var threadOut = stdOut.BeginRead(CurrentProcess.StandardOutput);
var threadErr = stdErr.BeginRead(CurrentProcess.StandardError);
var taskOut = stdOut.BeginRead(CurrentProcess.StandardOutput);
var taskErr = stdErr.BeginRead(CurrentProcess.StandardError);
var tcs = new TaskCompletionSource<CommandResult>();
CurrentProcess.Exited += (sender, arg) =>
{
threadOut.Join();
threadErr.Join();
Task.WaitAll(taskOut, taskErr);
var result = new CommandResult(
CurrentProcess.StartInfo,
CurrentProcess.ExitCode,

View file

@ -20,6 +20,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
private Mock<ICompiler> _nativeCompilerMock;
private List<ProjectContext> _contexts;
private BuildCommandApp _args;
private readonly WorkspaceContext _workspace;
public GivenACompilationDriverController()
{
@ -34,12 +35,13 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
.Compile(It.IsAny<ProjectContext>(), It.IsAny<BuildCommandApp>()))
.Returns(true);
_workspace = WorkspaceContext.Create(ProjectReaderSettings.ReadFromEnvironment(), designTime: false);
_contexts = new List<ProjectContext>
{
ProjectContext.Create(_projectJson, NuGetFramework.Parse("netcoreapp1.0"))
_workspace.GetProjectContext(_projectJson, NuGetFramework.Parse("netcoreapp1.0"))
};
_args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
_args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform", WorkspaceContext.Create(designTime: false));
}
[Fact]

View file

@ -188,7 +188,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
It.IsAny<string>()))
.Returns(command.Object);
var _args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform");
var _args = new BuildCommandApp("dotnet compile", ".NET Compiler", "Compiler for the .NET Platform", WorkspaceContext.Create(designTime: false));
_args.ConfigValue = ConfigValue;
PreCompileScriptVariables = new Dictionary<string, string>();
@ -219,7 +219,8 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests
rids.Add(rid);
}
var context = ProjectContext.Create(projectJson, TestAssetFramework, rids);
var workspace = WorkspaceContext.Create(ProjectReaderSettings.ReadFromEnvironment(), designTime: false);
var context = workspace.GetProjectContext(projectJson, TestAssetFramework, rids);
managedCompiler.Compile(context, _args);
RuntimeOutputDir = Path.Combine(OutputPath, rid);

View file

@ -228,7 +228,7 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
_writer.Dispose();
_networkStream.Dispose();
_readCancellationToken.Cancel();
try
{
_socket.Shutdown(SocketShutdown.Both);

View file

@ -506,13 +506,13 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
var testProjectRoot = Path.Combine(RepoRoot, "TestAssets", "ProjectModelServer", "MSBuildReferencesProjects", "ValidCase01");
foreach (var classLibrary in classLibraries)
{
dependencies.RetrieveDependency(classLibrary)
.AssertProperty("Type", LibraryType.MSBuildProject.ToString())
.AssertProperty("Path", NormalizePathString(Path.Combine(testProjectRoot, classLibrary, $"{classLibrary}.csproj")))
.AssertProperty<bool>("Resolved", true)
.AssertProperty("Name", classLibrary)
.AssertProperty<JArray>("Errors", array => array.Count == 0)
.AssertProperty<JArray>("Warnings", array => array.Count == 0);
var dependency = dependencies.RetrieveDependency(classLibrary);
dependency.AssertProperty("Type", LibraryType.MSBuildProject.ToString());
dependency.AssertProperty("Path", NormalizePathString(Path.Combine(testProjectRoot, classLibrary, $"{classLibrary}.csproj")));
dependency.AssertProperty<bool>("Resolved", true);
dependency.AssertProperty("Name", classLibrary);
dependency.AssertProperty<JArray>("Errors", array => array.Count == 0);
dependency.AssertProperty<JArray>("Warnings", array => array.Count == 0);
}
var references = messages.RetrieveSingleMessage(MessageTypes.References)
@ -632,12 +632,12 @@ namespace Microsoft.DotNet.ProjectModel.Server.Tests
afterDependencies.RetrieveDependency("ClassLibrary3");
}
}
[Fact]
public void TestMscorlibLibraryDuplication()
{
var projectPath = Path.Combine(RepoRoot, "TestAssets", "ProjectModelServer", "MscorlibLibraryDuplication");
using (var server = new DthTestServer(_loggerFactory))
using (var client = new DthTestClient(server, _loggerFactory))
{

View file

@ -28,7 +28,8 @@
}
},
"content": [
"../../TestAssets/TestProjects/ProjectWithTests/project.json"
"../../TestAssets/TestProjects/ProjectWithTests/project.json",
"../../TestAssets/TestProjects/ProjectWithTests/project.lock.json"
],
"testRunner": "xunit"
}