Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorMatt Ward <matt.ward@microsoft.com>2019-08-07 18:45:58 +0300
committermonojenkins <jo.shields+jenkins@xamarin.com>2019-08-08 10:29:28 +0300
commitb5ca7dd297d3551c67ac5b4823549cee8cadf8e3 (patch)
tree47e0a5388236e655a488153650d65f61c8f3723a /main
parentdad818d539c194683e5d93c93d8b35077dc868fd (diff)
[Core] Cache ProjectFile.Include to improve project save performance
With an SDK style project containing 1500 C# files, adding a new C# class to the project would result in the saving taking about 20 seconds. The majority of this time was spent in the Project's SaveProjectItems as it tried to determine if an MSBuild remove item should be added for the new file. Caching the ProjectFile.Include takes the save time down to around 300 ms. Fixes VSTS #947103 - Class creation is very slow in projects with hundreds of classes
Diffstat (limited to 'main')
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFile.cs32
-rw-r--r--main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/DotNetCoreProjectTests.cs38
-rw-r--r--main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs26
3 files changed, 96 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFile.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFile.cs
index b3f0717fdb..8c53b0504d 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFile.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectFile.cs
@@ -74,14 +74,22 @@ namespace MonoDevelop.Projects
BuildAction = buildAction;
}
+ string cachedInclude;
+
public override string Include {
get {
if (Project != null) {
+ if (cachedInclude != null)
+ return cachedInclude;
+
string path = MSBuildProjectService.ToMSBuildPath (Project.ItemDirectory, FilePath);
if (path.Length > 0) {
//directory paths must end with '/'
if ((Subtype == Subtype.Directory) && path [path.Length - 1] != '\\')
path = path + "\\";
+ // Cache the include path to avoid recalculating MSBuildProjectService.ToMSBuildPath
+ // which can slow down saving SDK style projects that contain thousands of files.
+ cachedInclude = path;
return path;
}
}
@@ -186,6 +194,8 @@ namespace MonoDevelop.Projects
if (IsLink && Link.FileName == oldPath.FileName)
link = Path.Combine (Path.GetDirectoryName (link), filename.FileName);
+ cachedInclude = null;
+
// If a file that belongs to a project is being renamed, update the value of UnevaluatedInclude
// since that is used when saving
if (Project != null)
@@ -474,15 +484,33 @@ namespace MonoDevelop.Projects
}
}
+ Project project;
+
protected override void OnProjectSet ()
{
base.OnProjectSet ();
+ if (project != null) {
+ project.Modified -= OnProjectModified;
+ project = null;
+ }
if (Project != null) {
base.Include = Include;
+ project = Project;
+ project.Modified += OnProjectModified;
VirtualPathChanged?.Invoke (this, new ProjectFileVirtualPathChangedEventArgs (this, FilePath.Null, ProjectVirtualPath));
}
}
+ void OnProjectModified (object sender, SolutionItemModifiedEventArgs e)
+ {
+ foreach (var eventInfo in e) {
+ if (eventInfo.Hint == "FileName") {
+ cachedInclude = null;
+ return;
+ }
+ }
+ }
+
public override string ToString ()
{
return "[ProjectFile: FileName=" + filename + "]";
@@ -503,6 +531,10 @@ namespace MonoDevelop.Projects
public virtual void Dispose ()
{
+ if (project != null) {
+ project.Modified -= OnProjectModified;
+ project = null;
+ }
}
internal event EventHandler<ProjectFileVirtualPathChangedEventArgs> VirtualPathChanged;
diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/DotNetCoreProjectTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/DotNetCoreProjectTests.cs
index 86a3aadfc2..0ba1113eb9 100644
--- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/DotNetCoreProjectTests.cs
+++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/DotNetCoreProjectTests.cs
@@ -696,6 +696,44 @@ namespace MonoDevelop.Projects
}
}
+ [Test]
+ public async Task AddNewFileToProjectAndSave_ProjectHas1500CSharpFiles_SavingIsFast ()
+ {
+ FilePath solFile = Util.GetSampleProject ("netstandard-sdk", "netstandard-sdk.sln");
+
+ // Create 1500 C# files.
+ var sourceDirectory = solFile.ParentDirectory.Combine ("Files");
+ Directory.CreateDirectory (sourceDirectory);
+
+ for (int i = 0; i < 1500; ++i) {
+ string fileName = sourceDirectory.Combine ($"Test{i}.cs");
+ string code = "class Test" + i + "{}";
+ File.WriteAllText (fileName, code);
+ }
+
+ using (var solution = (Solution)await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solFile)) {
+ var p = solution.GetAllProjects ().Single () as DotNetProject;
+ // Sanity check - ensure project and solution in same directory otherwise the generated .cs files
+ // will not be used.
+ Assert.AreEqual (solution.BaseDirectory, p.BaseDirectory);
+
+ string fileName = p.BaseDirectory.Combine ("NewClass.cs");
+ string code = "class NewClass {}";
+ File.WriteAllText (fileName, code);
+
+ p.AddFile (fileName, BuildAction.Compile);
+
+ var timer = Stopwatch.StartNew ();
+ await p.SaveAsync (Util.GetMonitor ());
+ timer.Stop ();
+
+ // This was taking 20 seconds before.
+ // Takes about 300ms with ProjectFile.Include caching. Here we use 2 seconds
+ // in case the build server is slow.
+ Assert.That (timer.ElapsedMilliseconds, Is.LessThan (2000));
+ }
+ }
+
static void RunMSBuildRestore (FilePath fileName)
{
CreateNuGetConfigFile (fileName.ParentDirectory);
diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs
index e44b98f5c6..0913f550de 100644
--- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs
+++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs
@@ -1226,6 +1226,32 @@ namespace MonoDevelop.Projects
}
}
+ /// <summary>
+ /// Ensure the cached value is updated when the Project or FileName changes.
+ /// </summary>
+ [Test]
+ public void ProjectFileIncludeCachingTests ()
+ {
+ using (var project = Services.ProjectService.CreateDotNetProject ("C#")) {
+ FilePath directory = Util.CreateTmpDir ("ProjectFileIncludeCachingTests");
+ project.FileName = directory.Combine ("Project.csproj");
+
+ var fileName = project.BaseDirectory.Combine ("Source", "Test.cs");
+ var projectFile = new ProjectFile (fileName);
+ projectFile.Project = project;
+
+ Assert.AreEqual (@"Source\Test.cs", projectFile.Include);
+
+ projectFile.Name = projectFile.FilePath.ChangeName ("Changed");
+ Assert.AreEqual (@"Source\Changed.cs", projectFile.Include);
+ Assert.AreEqual (@"Source\Changed.cs", projectFile.UnevaluatedInclude);
+
+ // Change project's ItemDirectory. This will change the ProjectFile's Include.
+ project.FileName = project.BaseDirectory.Combine ("Source", "Project.csproj");
+ Assert.AreEqual ("Changed.cs", projectFile.Include);
+ }
+ }
+
class TestGetReferencesProjectExtension : DotNetProjectExtension
{
protected internal override Task<List<AssemblyReference>> OnGetReferences (ConfigurationSelector configuration, CancellationToken token)