Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance for large projects #4419

Merged
merged 50 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d8b9cad
Do not let DafnyFile already open IO resources
keyboardDrummer Aug 15, 2023
8fb0305
Immediately cancel previous compilation when receiving a document update
keyboardDrummer Aug 15, 2023
b21c716
Added tests
keyboardDrummer Aug 15, 2023
409def1
Renames
keyboardDrummer Aug 17, 2023
f3cf4dd
Legacy symbol table differentiates between positions in different files
keyboardDrummer Aug 17, 2023
95a2c10
Update test
keyboardDrummer Aug 16, 2023
242655c
Merge branch 'betterLegacyMigration' into largeFilesTest
keyboardDrummer Aug 17, 2023
cbf2416
Rename test
keyboardDrummer Aug 17, 2023
8c0075f
Introduce cancellation to parsing
keyboardDrummer Aug 18, 2023
5d14e53
Draft stuff
keyboardDrummer Aug 18, 2023
1c9450b
Do not let DafnyFile already open IO resources
keyboardDrummer Aug 15, 2023
b1b0e53
Introduce cancellation to parsing
keyboardDrummer Aug 18, 2023
c82ae9a
Ran formatter
keyboardDrummer Aug 18, 2023
8f51171
Parses error handling fixes
keyboardDrummer Aug 18, 2023
c557c58
Merge branch 'master' into parserCancellation
keyboardDrummer Aug 18, 2023
982a46f
Merge branch 'master' into parserCancellation
keyboardDrummer Aug 18, 2023
6fbe5ac
Introduced throttle on IDE notifications
keyboardDrummer Aug 18, 2023
fa30f38
Performance fixes
keyboardDrummer Aug 18, 2023
f44be4e
Cleanup
keyboardDrummer Aug 18, 2023
1740437
Remove GetRootSourceUris cache
keyboardDrummer Aug 18, 2023
6230c66
Cleanup
keyboardDrummer Aug 18, 2023
167c6bf
Merge remote-tracking branch 'origin/master' into largeFilesTest
keyboardDrummer Aug 18, 2023
e3dd729
Make sure fix for WriterFromOutputHelper works across all packages by…
keyboardDrummer Aug 18, 2023
9e3c3c3
Merge remote-tracking branch 'fork/parserCancellation' into largeFile…
keyboardDrummer Aug 18, 2023
dacfa78
Cleanup
keyboardDrummer Aug 18, 2023
3e03403
Cleanup and fixes
keyboardDrummer Aug 19, 2023
a2a38bd
Merge commit '83a21338426~1' into largeFilesTest
keyboardDrummer Aug 19, 2023
dffa9a4
Merge commit '83a21338426' into largeFilesTest
keyboardDrummer Aug 19, 2023
5f4aeae
Refactoring
keyboardDrummer Aug 21, 2023
da1f335
Gutter icon tests are passing
keyboardDrummer Aug 21, 2023
77869b9
Fixes
keyboardDrummer Aug 21, 2023
e76531a
Test fixes
keyboardDrummer Aug 21, 2023
81c90d4
Fix used cancellationToken
keyboardDrummer Aug 21, 2023
f807e8e
Test improvement
keyboardDrummer Aug 21, 2023
304baef
Increase allowed division
keyboardDrummer Aug 21, 2023
00b2ebe
Ran formatter
keyboardDrummer Aug 21, 2023
9eb13d0
Optimize file search
keyboardDrummer Aug 21, 2023
4b26232
Fixes
keyboardDrummer Aug 22, 2023
db4e4b2
Comment out debug code
keyboardDrummer Aug 22, 2023
2a4f673
Optimize imports
keyboardDrummer Aug 22, 2023
897d4d6
Fix root source file finding
keyboardDrummer Aug 22, 2023
2c1f9e1
Increase accepted division
keyboardDrummer Aug 23, 2023
126dce8
Allow retries on the average scheduling time
keyboardDrummer Aug 23, 2023
ffa0aa8
Comment out debug line
keyboardDrummer Aug 23, 2023
3a39625
Merge branch 'master' into largeFilesTest
keyboardDrummer Aug 23, 2023
4c1391b
Fix comp error
keyboardDrummer Aug 23, 2023
cba5153
Merge remote-tracking branch 'origin/master' into largeFilesTest
keyboardDrummer Aug 23, 2023
d710f5c
Merge branch 'master' into largeFilesTest
keyboardDrummer Aug 24, 2023
13e1595
Update Source/DafnyCore/Generic/Util.cs
jtristan Aug 24, 2023
b866971
Remove drive-by fix so it can be put in another PR
keyboardDrummer Aug 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/DafnyCore.Test/NodeTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using Microsoft.Dafny;

namespace DafnyCore.Test;
namespace DafnyCore.Test;

public class NodeTests {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
// Copyright by the contributors to the Dafny Project
// SPDX-License-Identifier: MIT

#nullable disable
using System.IO;
#nullable enable
using System.Text;
using Microsoft.Extensions.Primitives;
using Xunit.Abstractions;

namespace DafnyTestGeneration.Test;
namespace DafnyCore.Test;

public class WriterFromOutputHelper : TextWriter {
private readonly StringBuilder buffer = new();
Expand All @@ -17,7 +12,7 @@ public WriterFromOutputHelper(ITestOutputHelper output) {
this.output = output;
}

public override void Write(string value) {
public override void Write(string? value) {
if (value != null) {
buffer.Append(value);
}
Expand All @@ -29,12 +24,12 @@ public override void Write(char value) {

public override Encoding Encoding => Encoding.Default;

public override void WriteLine(string value) {
public override void WriteLine(string? value) {
output.WriteLine(buffer + value);
buffer.Clear();
}

public override void WriteLine(string format, params object[] arg) {
public override void WriteLine(string format, params object?[] arg) {
output.WriteLine(buffer + format, arg);
buffer.Clear();
}
Expand Down
1 change: 0 additions & 1 deletion Source/DafnyCore/DafnyFile.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using DafnyCore;
Expand Down
60 changes: 45 additions & 15 deletions Source/DafnyCore/Options/DafnyProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,37 +56,67 @@ public IEnumerable<Uri> GetRootSourceUris(IFileSystem fileSystem) {
if (!Uri.IsFile) {
return new[] { Uri };
}
var matcher = GetMatcher(out var searchRoot);

var matcher = GetMatcher();

var diskRoot = Path.GetPathRoot(Uri.LocalPath);
var result = matcher.Execute(fileSystem.GetDirectoryInfoBase(diskRoot));
var files = result.Files.Select(f => Path.Combine(diskRoot, f.Path));
var result = matcher.Execute(fileSystem.GetDirectoryInfoBase(searchRoot));
var files = result.Files.Select(f => Path.Combine(searchRoot, f.Path));
return files.Select(file => new Uri(Path.GetFullPath(file)));
}

public bool ContainsSourceFile(Uri uri) {
var fileSystemWithSourceFile = new InMemoryDirectoryInfoFromDotNet8(Path.GetPathRoot(uri.LocalPath)!, new[] { uri.LocalPath });
return GetMatcher().Execute(fileSystemWithSourceFile).HasMatches;
var matcher = GetMatcher(out var searchRoot);
var fileSystemWithSourceFile = new InMemoryDirectoryInfoFromDotNet8(searchRoot, new[] { uri.LocalPath });
return matcher.Execute(fileSystemWithSourceFile).HasMatches;
}

private Matcher GetMatcher() {
private Matcher GetMatcher(out string commonRoot) {
var projectRoot = Path.GetDirectoryName(Uri.LocalPath)!;
var root = Path.GetPathRoot(Uri.LocalPath)!;
var diskRoot = Path.GetPathRoot(Uri.LocalPath)!;

var includes = Includes ?? new[] { "**/*.dfy" };
var excludes = Excludes ?? Array.Empty<string>();
var fullPaths = includes.Concat(excludes).Select(p => Path.GetFullPath(p, projectRoot)).ToList();
commonRoot = GetCommonParentDirectory(fullPaths) ?? diskRoot;
var matcher = new Matcher();
foreach (var includeGlob in Includes ?? new[] { "**/*.dfy" }) {
var fullPath = Path.GetFullPath(includeGlob, projectRoot);
matcher.AddInclude(Path.GetRelativePath(root, fullPath));
foreach (var includeGlob in includes) {
matcher.AddInclude(Path.GetRelativePath(commonRoot, Path.GetFullPath(includeGlob, projectRoot)));
}

foreach (var includeGlob in Excludes ?? Enumerable.Empty<string>()) {
var fullPath = Path.GetFullPath(includeGlob, projectRoot);
matcher.AddExclude(Path.GetRelativePath(root, fullPath));
foreach (var excludeGlob in excludes) {
matcher.AddExclude(Path.GetRelativePath(commonRoot, Path.GetFullPath(excludeGlob, projectRoot)));
}

return matcher;
}

string GetCommonParentDirectory(IReadOnlyList<string> strings) {
if (!strings.Any()) {
return null;
}
var commonPrefix = strings.FirstOrDefault() ?? "";

foreach (var newString in strings) {
var potentialMatchLength = Math.Min(newString.Length, commonPrefix.Length);

if (potentialMatchLength < commonPrefix.Length) {
commonPrefix = commonPrefix.Substring(0, potentialMatchLength);
}

for (var i = 0; i < potentialMatchLength; i++) {
if (newString[i] == '*' || newString[i] != commonPrefix[i]) {
commonPrefix = commonPrefix.Substring(0, i);
break;
}
}
}

if (!Path.EndsInDirectorySeparator(commonPrefix)) {
commonPrefix = Path.GetDirectoryName(commonPrefix);
}

return commonPrefix;
}

public void Validate(TextWriter outputWriter, IEnumerable<Option> possibleOptions) {
if (Options == null) {
return;
Expand Down
2 changes: 0 additions & 2 deletions Source/DafnyDriver/DafnyDoc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ public static DafnyDriver.ExitValue DoDocumenting(IReadOnlyList<DafnyFile> dafny
outputdir = DefaultOutputDir;
}



// Collect all the dafny files; dafnyFiles already includes files from a .toml project file
var exitValue = DafnyDriver.ExitValue.SUCCESS;
dafnyFiles = dafnyFiles.Concat(dafnyFolders.SelectMany(folderPath => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Serilog.Sinks.InMemory" Version="0.11.0" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand All @@ -25,6 +25,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DafnyCore.Test\DafnyCore.Test.csproj" />
<ProjectReference Include="..\DafnyLanguageServer\DafnyLanguageServer.csproj" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DafnyCore.Test;
using Microsoft.Dafny.LanguageServer.IntegrationTest.Various;
using Microsoft.Dafny.LanguageServer.Language;
using OmniSharp.Extensions.LanguageServer.Client;
Expand Down Expand Up @@ -94,6 +95,7 @@ public AnonymousDisposable(Action action) {

private Action<LanguageServerOptions> GetServerOptionsAction(Action<DafnyOptions> modifyOptions) {
var dafnyOptions = DafnyOptions.Create(output);
dafnyOptions.Set(ServerCommand.UpdateThrottling, 0);
modifyOptions?.Invoke(dafnyOptions);
ServerCommand.ConfigureDafnyOptionsForServer(dafnyOptions);
ApplyDefaultOptionValues(dafnyOptions);
Expand Down
111 changes: 111 additions & 0 deletions Source/DafnyLanguageServer.Test/Performance/LargeFilesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Dafny.LanguageServer.IntegrationTest.Extensions;
using Microsoft.Dafny.LanguageServer.IntegrationTest.Util;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using Range = OmniSharp.Extensions.LanguageServer.Protocol.Models.Range;

namespace Microsoft.Dafny.LanguageServer.IntegrationTest.Performance;

public class LargeFilesTest : ClientBasedLanguageServerTest {
protected override Task SetUp(Action<DafnyOptions> modifyOptions) {
return base.SetUp(options => {
modifyOptions?.Invoke(options);
options.Set(ServerCommand.UpdateThrottling, ServerCommand.DefaultThrottleTime);
});
}

/// <summary>
/// This test should complete in less than a second, because it opens a moderately sized file
/// on which it makes edits so quickly that the edits should all be processed in a single compilation.
/// And a single compilation should complete in less than 200ms.
///
/// Right now the test still takes more than 5 seconds.
/// To reduce runtime of this test,
jtristan marked this conversation as resolved.
Show resolved Hide resolved
/// remove ReportRealtimeDiagnostics (since it is called 10 times for each update,
/// and calls InitialIdeState, which often calls CompilationAfterResolution.ToIdeState (expensive))
/// </summary>
[Fact]
public async Task QuickEditsInLargeFile() {
string GetLineContent(int index) => $"method Foo{index}() {{ assume false; }}";
var contentBuilder = new StringBuilder();
for (int lineNumber = 0; lineNumber < 1000; lineNumber++) {
contentBuilder.AppendLine(GetLineContent(lineNumber));
}
var source = contentBuilder.ToString();

double lowest = double.MaxValue;
Exception lastException = null;
try {
for (int attempt = 0; attempt < 10; attempt++) {
var cancelSource = new CancellationTokenSource();
var measurementTask = AssertThreadPoolIsAvailable(cancelSource.Token);
var beforeOpen = DateTime.Now;
var documentItem = await CreateAndOpenTestDocument(source, "ManyFastEditsUsingLargeFiles.dfy",
cancellationToken: CancellationTokenWithHighTimeout);
var afterOpen = DateTime.Now;
var openMilliseconds = (afterOpen - beforeOpen).TotalMilliseconds;
for (int i = 0; i < 100; i++) {
ApplyChange(ref documentItem, new Range(0, 0, 0, 0), "// added this comment\n");
}

await client.WaitForNotificationCompletionAsync(documentItem.Uri, CancellationTokenWithHighTimeout);
var afterChange = DateTime.Now;
var changeMilliseconds = (afterChange - afterOpen).TotalMilliseconds;
await AssertNoDiagnosticsAreComing(CancellationTokenWithHighTimeout);
cancelSource.Cancel();
var averageTimeToSchedule = await measurementTask;
var division = changeMilliseconds / openMilliseconds;
lowest = Math.Min(lowest, division);

// Commented code left in intentionally
// await output.WriteLineAsync("openMilliseconds: " + openMilliseconds);
// await output.WriteLineAsync("changeMilliseconds: " + changeMilliseconds);
// await output.WriteLineAsync("division: " + division);
try {
Assert.True(averageTimeToSchedule < 100);
// Migration should be constant time, which would allow this number to be about 1.
// Right now migration is still slow so this has been set to 10 so the test can pass.
var changeTimeMultiplier = 15;
Assert.True(division < changeTimeMultiplier,
$"changeMilliseconds {changeMilliseconds}, openMilliseconds {openMilliseconds}");

return;
} catch (AssertActualExpectedException e) {
lastException = e;
}
}
} catch (OperationCanceledException) {
}

await output.WriteLineAsync("lowest division " + lowest);
throw lastException!;
}

private async Task<double> AssertThreadPoolIsAvailable(CancellationToken durationToken) {
int ticks = 0;
var waitTime = 100;
var start = DateTime.Now;
while (!durationToken.IsCancellationRequested) {
await Task.Run(() => {
Thread.Sleep(waitTime);
});
ticks++;
}

var end = DateTime.Now;
var span = end - start;
var totalSleepTime = ticks * waitTime;
var totalSchedulingTime = span.TotalMilliseconds - totalSleepTime;
var averageTimeToSchedule = totalSchedulingTime / ticks;
// await output.WriteLineAsync($"averageTimeToSchedule: {averageTimeToSchedule:0.##}");
return averageTimeToSchedule;
}

public LargeFilesTest(ITestOutputHelper output) : base(output) {
}
}
21 changes: 14 additions & 7 deletions Source/DafnyLanguageServer.Test/ProjectFiles/ProjectFilesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,30 @@ method Foo() {
}

[Fact]
public async Task FileOnlyAttachedToProjectFileThatAppliesToIt() {
public async Task FileOnlyAttachedToProjectFileThatIncludesIt() {
await SetUp(options => options.WarnShadowing = false);

var projectFileSource = @"
var outerSource = @"
[options]
warn-shadowing = true
";
var directory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var outerProjectFile = CreateTestDocument(projectFileSource, Path.Combine(directory, DafnyProject.FileName));
var outerProjectFile = CreateTestDocument(outerSource, Path.Combine(directory, DafnyProject.FileName));
await client.OpenDocumentAndWaitAsync(outerProjectFile, CancellationToken);

var innerProjectFile = CreateTestDocument("includes = []", Path.Combine(directory, "nested", DafnyProject.FileName));
var innerDirectory = Path.Combine(directory, "nested");
var innerProjectFile = CreateTestDocument("includes = []", Path.Combine(innerDirectory, DafnyProject.FileName));
await client.OpenDocumentAndWaitAsync(innerProjectFile, CancellationToken);

var filePath = Path.Combine(Directory.GetCurrentDirectory(), "ProjectFiles/TestFiles/noWarnShadowing.dfy");
var source = await File.ReadAllTextAsync(filePath);
var documentItem = CreateTestDocument(source, Path.Combine(directory, "nested/A.dfy"));
var source = @"
method Foo() {
var x := 3;
if (true) {
var x := 4;
}
}
";
var documentItem = CreateTestDocument(source, Path.Combine(innerDirectory, "A.dfy"));
await client.OpenDocumentAndWaitAsync(documentItem, CancellationToken);
var diagnostics = await GetLastDiagnostics(documentItem, CancellationToken);
Assert.Single(diagnostics);
Expand Down
27 changes: 1 addition & 26 deletions Source/DafnyLanguageServer.Test/StandaloneServerTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Text;
using DafnyCore.Test;
using Microsoft.Dafny.LanguageServer.Workspace;
using Xunit.Abstractions;
using Xunit;
Expand All @@ -21,29 +22,3 @@ public void OptionParsing() {
Assert.Equal(VerifyOnMode.Save, options.Get(ServerCommand.Verification));
}
}

public class WriterFromOutputHelper : TextWriter {
private readonly ITestOutputHelper output;
private bool failed = false;

public WriterFromOutputHelper(ITestOutputHelper output) {
this.output = output;
}

public override void Write(char value) {
if (!failed) {
failed = true;
WriteLine("Error: tried to write a single character, which WriterFromOutputHelper does not support.");
}
}

public override Encoding Encoding => Encoding.Default;

public override void WriteLine(string value) {
output.WriteLine(value);
}

public override void WriteLine(string format, params object[] arg) {
output.WriteLine(format, arg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -924,16 +924,17 @@ method test() {
".TrimStart();
var documentItem = CreateTestDocument(source, "IncrementalVerificationDiagnosticsBetweenMethods.dfy");
client.OpenDocument(documentItem);
var firstVerificationDiagnostics = await diagnosticsReceiver.AwaitNextDiagnosticsAsync(CancellationToken, documentItem);
var firstVerificationDiagnostics = await diagnosticsReceiver.AwaitNextNotificationAsync(CancellationToken);
try {
var secondVerificationDiagnostics =
await diagnosticsReceiver.AwaitNextDiagnosticsAsync(CancellationToken, documentItem);

Assert.Single(firstVerificationDiagnostics);
Assert.Single(firstVerificationDiagnostics.Diagnostics);
// Second diagnostic is a timeout exception from SlowToVerify
Assert.Equal(2, secondVerificationDiagnostics.Length);
} catch (OperationCanceledException) {
await output.WriteLineAsync($"firstVerificationDiagnostics: {firstVerificationDiagnostics.Stringify()}");
WriteVerificationHistory();
}
await AssertNoDiagnosticsAreComing(CancellationToken);
}
Expand Down
Loading
Loading