Skip to content

Commit 1c2a0f4

Browse files
authored
Add Razor TagHelper Parsing Benchmarks (#23627)
* Add Razor TagHelper Parsing Benchmarks Benchmarks: | Method | Mean | Error | StdDev | Op/s | Allocated | |---------------------------------------- |------------:|----------:|----------:|---------:|----------:| | 'TagHelper Design Time Processing' | 2,331.51 us | 28.916 us | 27.048 us | 428.9 | 985.33 KB | | 'TagHelper Component Directive Parsing' | 90.46 us | 0.472 us | 0.394 us | 11,055.1 | 3.01 KB | Notes / Attributions: - `BlazorServerTagHelpers` is just a demo file concatonated from the basic `BlazorServer` files - `taghelpers.json` was updated from: https://github.com/dotnet/aspnetcore-tooling/blob/73c96f1c00b90e0f9d4fdbfb0905376229ceb913/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/taghelpers.json - `ReadTagHelpers` was copied over from https://github.com/dotnet/aspnetcore-tooling/blob/fef50ba62387cb87d34970b4c88290c233361927/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs#L83-L93 Fixes: https://github.com/dotnet/aspnetcore/issues/23454
1 parent 17b01ae commit 1c2a0f4

File tree

5 files changed

+196
-11
lines changed

5 files changed

+196
-11
lines changed

src/Razor/Microsoft.AspNetCore.Razor.Language/src/DefaultRazorTagHelperBinderPhase.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ public ComponentDirectiveVisitor(string filePath, IReadOnlyList<TagHelperDescrip
247247
{
248248
// If this is a child content tag helper, we want to add it if it's original type is in scope.
249249
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in scope.
250-
TrySplitNamespaceAndType(typeName, out var typeNameTextSpan, out var _);
251-
typeName = GetTextSpanContent(typeNameTextSpan, typeName);
250+
TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _);
251+
typeName = GetTextSpanContent(namespaceTextSpan, typeName);
252252
}
253253

254254
if (currentNamespace != null && IsTypeInScope(typeName, currentNamespace))
@@ -337,8 +337,8 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
337337
{
338338
// If this is a child content tag helper, we want to add it if it's original type is in scope of the given namespace.
339339
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in this namespace.
340-
TrySplitNamespaceAndType(typeName, out var typeNameTextSpan, out var _);
341-
typeName = GetTextSpanContent(typeNameTextSpan, typeName);
340+
TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _);
341+
typeName = GetTextSpanContent(namespaceTextSpan, typeName);
342342
}
343343
if (typeName != null && IsTypeInNamespace(typeName, @namespace))
344344
{
@@ -352,13 +352,13 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
352352

353353
internal static bool IsTypeInNamespace(string typeName, string @namespace)
354354
{
355-
if (!TrySplitNamespaceAndType(typeName, out var typeNamespace, out var _) || typeNamespace.Length == 0)
355+
if (!TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _) || namespaceTextSpan.Length == 0)
356356
{
357357
// Either the typeName is not the full type name or this type is at the top level.
358358
return true;
359359
}
360360

361-
return @namespace.Length == typeNamespace.Length && 0 == string.CompareOrdinal(typeName, typeNamespace.Start, @namespace, 0, @namespace.Length);
361+
return @namespace.Length == namespaceTextSpan.Length && 0 == string.CompareOrdinal(typeName, namespaceTextSpan.Start, @namespace, 0, @namespace.Length);
362362
}
363363

364364
// Check if the given type is already in scope given the namespace of the current document.
@@ -368,13 +368,13 @@ internal static bool IsTypeInNamespace(string typeName, string @namespace)
368368
// Whereas `MyComponents.SomethingElse.OtherComponent` is not in scope.
369369
internal static bool IsTypeInScope(string typeName, string currentNamespace)
370370
{
371-
if (!TrySplitNamespaceAndType(typeName, out var typeNamespaceTextSpan, out var _) || typeNamespaceTextSpan.Length == 0)
371+
if (!TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _) || namespaceTextSpan.Length == 0)
372372
{
373373
// Either the typeName is not the full type name or this type is at the top level.
374374
return true;
375375
}
376376

377-
var typeNamespace = GetTextSpanContent(typeNamespaceTextSpan, typeName);
377+
var typeNamespace = GetTextSpanContent(namespaceTextSpan, typeName);
378378
var typeNamespaceSegments = typeNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
379379
var currentNamespaceSegments = currentNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
380380
if (typeNamespaceSegments.Length > currentNamespaceSegments.Length)
@@ -402,8 +402,8 @@ internal static bool IsTagHelperFromMangledClass(TagHelperDescriptor tagHelper)
402402
{
403403
// If this is a child content tag helper, we want to look at it's original type.
404404
// E.g, if the type name is `Test.__generated__MyComponent.ChildContent`, we want to look at `Test.__generated__MyComponent`.
405-
TrySplitNamespaceAndType(typeName, out var typeNameTextSpan, out var _);
406-
typeName = GetTextSpanContent(typeNameTextSpan, typeName);
405+
TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _);
406+
typeName = GetTextSpanContent(namespaceTextSpan, typeName);
407407
}
408408
if (!TrySplitNamespaceAndType(typeName, out var _, out var classNameTextSpan))
409409
{
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
@page "/BlazorServerTagHelpers"
2+
3+
4+
@using blazorserver_clean.Data
5+
@inject WeatherForecastService ForecastService
6+
7+
8+
<h1>Counter</h1>
9+
10+
<p>Current count: @currentCount</p>
11+
12+
@{var someVariable = true;} @someVariable
13+
14+
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
15+
16+
@code {
17+
private int currentCount = 0;
18+
19+
private void IncrementCount()
20+
{
21+
currentCount++;
22+
}
23+
}
24+
25+
<button class="btn btn-primary" @onkeypress:preventDefault @onclick="IncrementCount">Click me</button>
26+
27+
28+
<h1>Weather forecast</h1>
29+
30+
<p>This component demonstrates fetching data from a service.</p>
31+
32+
@if (forecasts == null)
33+
{
34+
<p><em>Loading...</em></p>
35+
}
36+
else
37+
{
38+
<table class="table">
39+
<thead>
40+
<tr>
41+
<th>Date</th>
42+
<th>Temp. (C)</th>
43+
<th>Temp. (F)</th>
44+
<th>Summary</th>
45+
</tr>
46+
</thead>
47+
<tbody>
48+
@foreach (var forecast in forecasts)
49+
{
50+
<tr>
51+
<td>@forecast.Date.ToShortDateString()</td>
52+
<td>@forecast.TemperatureC</td>
53+
<td>@forecast.TemperatureF</td>
54+
<td>@forecast.Summary</td>
55+
</tr>
56+
}
57+
</tbody>
58+
</table>
59+
}
60+
61+
@code {
62+
private WeatherForecast[] forecasts;
63+
64+
protected override async Task OnInitializedAsync()
65+
{
66+
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
67+
}
68+
}
69+
70+
<div class="top-row pl-4 navbar navbar-dark">
71+
<a class="navbar-brand" href="">blazorserver_clean</a>
72+
<button class="navbar-toggler" @onclick="ToggleNavMenu">
73+
<span class="navbar-toggler-icon"></span>
74+
</button>
75+
</div>
76+
77+
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
78+
<ul class="nav flex-column">
79+
<li class="nav-item px-3">
80+
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
81+
<span class="oi oi-home" aria-hidden="true"></span> Home
82+
</NavLink>
83+
</li>
84+
<li class="nav-item px-3">
85+
<NavLink class="nav-link" href="counter">
86+
<span class="oi oi-plus" aria-hidden="true"></span> Counter
87+
</NavLink>
88+
</li>
89+
<li class="nav-item px-3">
90+
<NavLink class="nav-link" href="fetchdata">
91+
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
92+
</NavLink>
93+
</li>
94+
</ul>
95+
</div>
96+
97+
@code {
98+
private bool collapseNavMenu = true;
99+
100+
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
101+
102+
private void ToggleNavMenu()
103+
{
104+
collapseNavMenu = !collapseNavMenu;
105+
}
106+
}

src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@
1616

1717
<ItemGroup>
1818
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
19+
<None Include="MSN.cshtml" CopyToOutputDirectory="PreserveNewest" />
20+
<None Include="taghelpers.json" CopyToOutputDirectory="PreserveNewest" />
21+
<None Include="BlazorServerTagHelpers.razor" CopyToOutputDirectory="PreserveNewest" />
22+
<Compile Include="$(SharedSourceRoot)RazorShared\TagHelperDescriptorJsonConverter.cs">
23+
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
24+
</Compile>
25+
<Compile Include="$(SharedSourceRoot)RazorShared\RazorDiagnosticJsonConverter.cs">
26+
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
27+
</Compile>
1928
</ItemGroup>
2029

2130
</Project>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 BenchmarkDotNet.Attributes;
8+
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
9+
using Microsoft.AspNetCore.Razor.Language;
10+
using Microsoft.CodeAnalysis.Razor.Serialization;
11+
using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
12+
using Newtonsoft.Json;
13+
using static Microsoft.AspNetCore.Razor.Language.DefaultRazorTagHelperBinderPhase;
14+
15+
namespace Microsoft.AspNetCore.Razor.Performance
16+
{
17+
public class RazorTagHelperParsingBenchmark
18+
{
19+
public RazorTagHelperParsingBenchmark()
20+
{
21+
var current = new DirectoryInfo(AppContext.BaseDirectory);
22+
while (current != null && !File.Exists(Path.Combine(current.FullName, "taghelpers.json")))
23+
{
24+
current = current.Parent;
25+
}
26+
27+
var root = current;
28+
29+
var tagHelpers = ReadTagHelpers(Path.Combine(root.FullName, "taghelpers.json"));
30+
var blazorServerTagHelpersFilePath = Path.Combine(root.FullName, "BlazorServerTagHelpers.razor");
31+
32+
var fileSystem = RazorProjectFileSystem.Create(root.FullName);
33+
ProjectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, b => RazorExtensions.Register(b));
34+
BlazorServerTagHelpersDemoFile = fileSystem.GetItem(Path.Combine(blazorServerTagHelpersFilePath), FileKinds.Legacy);
35+
36+
ComponentDirectiveVisitor = new ComponentDirectiveVisitor(blazorServerTagHelpersFilePath, tagHelpers, currentNamespace: null);
37+
var codeDocument = ProjectEngine.ProcessDesignTime(BlazorServerTagHelpersDemoFile);
38+
SyntaxTree = codeDocument.GetSyntaxTree();
39+
}
40+
41+
private RazorProjectEngine ProjectEngine { get; }
42+
private RazorProjectItem BlazorServerTagHelpersDemoFile { get; }
43+
private ComponentDirectiveVisitor ComponentDirectiveVisitor { get; }
44+
private RazorSyntaxTree SyntaxTree { get; }
45+
46+
[Benchmark(Description = "TagHelper Design Time Processing")]
47+
public void TagHelper_ProcessDesignTime()
48+
{
49+
_ = ProjectEngine.ProcessDesignTime(BlazorServerTagHelpersDemoFile);
50+
}
51+
52+
[Benchmark(Description = "TagHelper Component Directive Parsing")]
53+
public void TagHelper_ComponentDirectiveVisitor()
54+
{
55+
ComponentDirectiveVisitor.Visit(SyntaxTree);
56+
}
57+
58+
private static IReadOnlyList<TagHelperDescriptor> ReadTagHelpers(string filePath)
59+
{
60+
var serializer = new JsonSerializer();
61+
serializer.Converters.Add(new RazorDiagnosticJsonConverter());
62+
serializer.Converters.Add(new TagHelperDescriptorJsonConverter());
63+
64+
using (var reader = new JsonTextReader(File.OpenText(filePath)))
65+
{
66+
return serializer.Deserialize<IReadOnlyList<TagHelperDescriptor>>(reader);
67+
}
68+
}
69+
}
70+
}

src/Razor/perf/Microsoft.AspNetCore.Razor.Performance/taghelpers.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)