Skip to content

Commit 9b911cb

Browse files
committed
Add some benchmarks for project system
Adds a few benchmarks for things we're working on, also addressed a few small perf issues that have an impact for large documents.
1 parent 63af922 commit 9b911cb

File tree

14 files changed

+558
-23
lines changed

14 files changed

+558
-23
lines changed

benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@
1717
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
1818
<Link>Serialization\%(FileName)%(Extension)</Link>
1919
</Compile>
20+
<Compile Include="..\..\test\Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common\TestServices.cs">
21+
<Link>TestServices\%(FileName)%(Extension)</Link>
22+
</Compile>
23+
<Compile Include="..\..\test\Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common\TestWorkspace.cs">
24+
<Link>TestServices\%(FileName)%(Extension)</Link>
25+
</Compile>
26+
<Compile Include="..\..\test\Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common\TestLanguageServices.cs">
27+
<Link>TestServices\%(FileName)%(Extension)</Link>
28+
</Compile>
29+
<Compile Include="..\..\test\Microsoft.CodeAnalysis.Razor.Workspaces.Test.Common\TestWorkspaceServices.cs">
30+
<Link>TestServices\%(FileName)%(Extension)</Link>
31+
</Compile>
2032
</ItemGroup>
2133

2234
<ItemGroup>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Threading.Tasks;
6+
using BenchmarkDotNet.Attributes;
7+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
8+
9+
namespace Microsoft.AspNetCore.Razor.Performance
10+
{
11+
public class BackgroundCodeGenerationBenchmark : ProjectSnapshotManagerBenchmarkBase
12+
{
13+
[IterationSetup]
14+
public void Setup()
15+
{
16+
SnapshotManager = CreateProjectSnapshotManager();
17+
SnapshotManager.HostProjectAdded(HostProject);
18+
SnapshotManager.Changed += SnapshotManager_Changed;
19+
}
20+
21+
[IterationCleanup]
22+
public void Cleanup()
23+
{
24+
SnapshotManager.Changed -= SnapshotManager_Changed;
25+
26+
Tasks.Clear();
27+
}
28+
29+
private List<Task> Tasks { get; } = new List<Task>();
30+
31+
private DefaultProjectSnapshotManager SnapshotManager { get; set; }
32+
33+
[Benchmark(Description = "Generates the code for 100 files", OperationsPerInvoke = 100)]
34+
public async Task BackgroundCodeGeneration_Generate100Files()
35+
{
36+
for (var i = 0; i < Documents.Length; i++)
37+
{
38+
SnapshotManager.DocumentAdded(HostProject, Documents[i], TextLoaders[i % 4]);
39+
}
40+
41+
await Task.WhenAll(Tasks);
42+
}
43+
44+
private void SnapshotManager_Changed(object sender, ProjectChangeEventArgs e)
45+
{
46+
// The real work happens here.
47+
var project = SnapshotManager.GetLoadedProject(e.ProjectFilePath);
48+
var document = project.GetDocument(e.DocumentFilePath);
49+
50+
Tasks.Add(document.GetGeneratedOutputAsync());
51+
}
52+
}
53+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using BenchmarkDotNet.Attributes;
5+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
6+
7+
namespace Microsoft.AspNetCore.Razor.Performance
8+
{
9+
public class ProjectLoadBenchmark : ProjectSnapshotManagerBenchmarkBase
10+
{
11+
[IterationSetup]
12+
public void Setup()
13+
{
14+
SnapshotManager = CreateProjectSnapshotManager();
15+
}
16+
17+
private DefaultProjectSnapshotManager SnapshotManager { get; set; }
18+
19+
[Benchmark(Description = "Initializes a project and 100 files", OperationsPerInvoke = 100)]
20+
public void ProjectLoad_AddProjectAnd100Files()
21+
{
22+
SnapshotManager.HostProjectAdded(HostProject);
23+
24+
for (var i= 0; i < Documents.Length; i++)
25+
{
26+
SnapshotManager.DocumentAdded(HostProject, Documents[i], TextLoaders[i % 4]);
27+
}
28+
}
29+
}
30+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
10+
using Microsoft.AspNetCore.Razor.Language;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.Host;
13+
using Microsoft.CodeAnalysis.Razor;
14+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
15+
using Microsoft.CodeAnalysis.Text;
16+
using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
17+
using Newtonsoft.Json;
18+
19+
namespace Microsoft.AspNetCore.Razor.Performance
20+
{
21+
public class ProjectSnapshotManagerBenchmarkBase
22+
{
23+
public ProjectSnapshotManagerBenchmarkBase()
24+
{
25+
var current = new DirectoryInfo(AppContext.BaseDirectory);
26+
while (current != null && !File.Exists(Path.Combine(current.FullName, "Razor.sln")))
27+
{
28+
current = current.Parent;
29+
}
30+
31+
var root = current;
32+
var projectRoot = Path.Combine(root.FullName, "test", "testapps", "LargeProject");
33+
34+
HostProject = new HostProject(Path.Combine(projectRoot, "LargeProject.csproj"), FallbackRazorConfiguration.MVC_2_1);
35+
36+
TextLoaders = new TextLoader[4];
37+
for (var i = 0; i < 4; i++)
38+
{
39+
var filePath = Path.Combine(projectRoot, "Views", "Home", $"View00{i % 4}.cshtml");
40+
var text = SourceText.From(filePath, encoding: null);
41+
TextLoaders[i] = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()));
42+
}
43+
44+
Documents = new HostDocument[100];
45+
for (var i = 0; i < Documents.Length; i++)
46+
{
47+
var filePath = Path.Combine(projectRoot, "Views", "Home", $"View00{i % 4}.cshtml");
48+
Documents[i] = new HostDocument(filePath, $"/Views/Home/View00{i}.cshtml");
49+
}
50+
51+
var tagHelpers = Path.Combine(root.FullName, "benchmarks", "Microsoft.AspNetCore.Razor.Performance", "taghelpers.json");
52+
TagHelperResolver = new StaticTagHelperResolver(ReadTagHelpers(tagHelpers));
53+
}
54+
55+
internal HostProject HostProject { get; }
56+
57+
internal HostDocument[] Documents { get; }
58+
59+
internal TextLoader[] TextLoaders { get; }
60+
61+
internal TagHelperResolver TagHelperResolver { get; }
62+
63+
internal DefaultProjectSnapshotManager CreateProjectSnapshotManager()
64+
{
65+
var services = TestServices.Create(
66+
new IWorkspaceService[]
67+
{
68+
new StaticProjectSnapshotProjectEngineFactory(),
69+
},
70+
new ILanguageService[]
71+
{
72+
TagHelperResolver,
73+
});
74+
75+
return new DefaultProjectSnapshotManager(
76+
new TestForegroundDispatcher(),
77+
new TestErrorReporter(),
78+
Array.Empty<ProjectSnapshotChangeTrigger>(),
79+
new AdhocWorkspace(services));
80+
}
81+
82+
private static IReadOnlyList<TagHelperDescriptor> ReadTagHelpers(string filePath)
83+
{
84+
var serializer = new JsonSerializer();
85+
serializer.Converters.Add(new RazorDiagnosticJsonConverter());
86+
serializer.Converters.Add(new TagHelperDescriptorJsonConverter());
87+
88+
using (var reader = new JsonTextReader(File.OpenText(filePath)))
89+
{
90+
return serializer.Deserialize<IReadOnlyList<TagHelperDescriptor>>(reader);
91+
}
92+
}
93+
94+
private class TestForegroundDispatcher : ForegroundDispatcher
95+
{
96+
public override bool IsForegroundThread => true;
97+
98+
public override TaskScheduler ForegroundScheduler => TaskScheduler.Default;
99+
100+
public override TaskScheduler BackgroundScheduler => TaskScheduler.Default;
101+
}
102+
103+
private class TestErrorReporter : ErrorReporter
104+
{
105+
public override void ReportError(Exception exception)
106+
{
107+
}
108+
109+
public override void ReportError(Exception exception, ProjectSnapshot project)
110+
{
111+
}
112+
113+
public override void ReportError(Exception exception, Project workspaceProject)
114+
{
115+
}
116+
}
117+
118+
private class StaticTagHelperResolver : TagHelperResolver
119+
{
120+
private readonly IReadOnlyList<TagHelperDescriptor> _tagHelpers;
121+
122+
public StaticTagHelperResolver(IReadOnlyList<TagHelperDescriptor> tagHelpers)
123+
{
124+
this._tagHelpers = tagHelpers;
125+
}
126+
127+
public override Task<TagHelperResolutionResult> GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
128+
{
129+
return Task.FromResult(new TagHelperResolutionResult(_tagHelpers, Array.Empty<RazorDiagnostic>()));
130+
}
131+
}
132+
133+
private class StaticProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory
134+
{
135+
public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
136+
{
137+
return RazorProjectEngine.Create(project.Configuration, fileSystem, b =>
138+
{
139+
RazorExtensions.Register(b);
140+
});
141+
}
142+
143+
public override IProjectEngineFactory FindFactory(ProjectSnapshot project)
144+
{
145+
throw new NotImplementedException();
146+
}
147+
148+
public override IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project)
149+
{
150+
throw new NotImplementedException();
151+
}
152+
}
153+
}
154+
}

src/Microsoft.AspNetCore.Razor.Language/Legacy/ParserHelpers.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ internal static class ParserHelpers
1919
'\u2029' // Paragraph separator
2020
};
2121

22-
public static bool IsNewLine(char value)
23-
{
24-
return NewLineCharacters.Contains(value);
25-
}
22+
public static bool IsNewLine(char value) => Array.IndexOf<char>(NewLineCharacters, value) != -1;
2623

2724
public static bool IsNewLine(string value)
2825
{

src/Microsoft.AspNetCore.Razor.Language/Legacy/SeekableTextReader.cs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ internal class SeekableTextReader : TextReader, ITextDocument
1010
{
1111
private readonly LineTrackingStringBuffer _buffer;
1212
private int _position = 0;
13+
private int _current;
1314
private SourceLocation _location;
14-
private char? _current;
1515

1616
public SeekableTextReader(string source, string filePath) : this(source.ToCharArray(), filePath) { }
1717

@@ -24,8 +24,6 @@ public SeekableTextReader(char[] source, string filePath)
2424

2525
_buffer = new LineTrackingStringBuffer(source, filePath);
2626
UpdateState();
27-
28-
_location = new SourceLocation(filePath, 0, 0, 0);
2927
}
3028

3129
public SourceLocation Location => _location;
@@ -47,24 +45,13 @@ public int Position
4745

4846
public override int Read()
4947
{
50-
if (_current == null)
51-
{
52-
return -1;
53-
}
54-
var chr = _current.Value;
48+
var c = _current;
5549
_position++;
5650
UpdateState();
57-
return chr;
51+
return c;
5852
}
5953

60-
public override int Peek()
61-
{
62-
if (_current == null)
63-
{
64-
return -1;
65-
}
66-
return _current.Value;
67-
}
54+
public override int Peek() => _current;
6855

6956
private void UpdateState()
7057
{
@@ -76,12 +63,12 @@ private void UpdateState()
7663
}
7764
else if (_buffer.Length == 0)
7865
{
79-
_current = null;
66+
_current = -1;
8067
_location = SourceLocation.Zero;
8168
}
8269
else
8370
{
84-
_current = null;
71+
_current = -1;
8572
_location = _buffer.EndLocation;
8673
}
8774
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<_RazorMSBuildRoot>$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\bin\$(Configuration)\</_RazorMSBuildRoot>
5+
</PropertyGroup>
6+
7+
<Import Project="$(SolutionRoot)src\Microsoft.AspNetCore.Razor.Design\build\netstandard2.0\Microsoft.AspNetCore.Razor.Design.props" />
8+
9+
<PropertyGroup>
10+
<!-- Override for the MVC extension -->
11+
<_MvcExtensionAssemblyPath>$(SolutionRoot)src\Microsoft.AspNetCore.Mvc.Razor.Extensions\bin\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll</_MvcExtensionAssemblyPath>
12+
</PropertyGroup>
13+
<Import Project="$(SolutionRoot)src\Microsoft.AspNetCore.Mvc.Razor.Extensions\build\netstandard2.0\Microsoft.AspNetCore.Mvc.Razor.Extensions.props" />
14+
15+
<PropertyGroup>
16+
<TargetFramework>netcoreapp2.2</TargetFramework>
17+
</PropertyGroup>
18+
19+
<!-- Test Placeholder -->
20+
21+
<Import Project="$(SolutionRoot)src\Microsoft.AspNetCore.Mvc.Razor.Extensions\build\netstandard2.0\Microsoft.AspNetCore.Mvc.Razor.Extensions.targets" />
22+
23+
</Project>

0 commit comments

Comments
 (0)