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:73c96f1c00/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/taghelpers.json- `ReadTagHelpers` was copied over fromfef50ba623/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs (L83-L93)Fixes: https://github.com/dotnet/aspnetcore/issues/23454
This commit is contained in:
parent
17b01ae667
commit
1c2a0f4fe6
|
|
@ -247,8 +247,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
// If this is a child content tag helper, we want to add it if it's original type is in scope.
|
||||
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in scope.
|
||||
TrySplitNamespaceAndType(typeName, out var typeNameTextSpan, out var _);
|
||||
typeName = GetTextSpanContent(typeNameTextSpan, typeName);
|
||||
TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _);
|
||||
typeName = GetTextSpanContent(namespaceTextSpan, typeName);
|
||||
}
|
||||
|
||||
if (currentNamespace != null && IsTypeInScope(typeName, currentNamespace))
|
||||
|
|
@ -337,8 +337,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
// 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.
|
||||
// E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in this namespace.
|
||||
TrySplitNamespaceAndType(typeName, out var typeNameTextSpan, out var _);
|
||||
typeName = GetTextSpanContent(typeNameTextSpan, typeName);
|
||||
TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _);
|
||||
typeName = GetTextSpanContent(namespaceTextSpan, typeName);
|
||||
}
|
||||
if (typeName != null && IsTypeInNamespace(typeName, @namespace))
|
||||
{
|
||||
|
|
@ -352,13 +352,13 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
|
||||
internal static bool IsTypeInNamespace(string typeName, string @namespace)
|
||||
{
|
||||
if (!TrySplitNamespaceAndType(typeName, out var typeNamespace, out var _) || typeNamespace.Length == 0)
|
||||
if (!TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _) || namespaceTextSpan.Length == 0)
|
||||
{
|
||||
// Either the typeName is not the full type name or this type is at the top level.
|
||||
return true;
|
||||
}
|
||||
|
||||
return @namespace.Length == typeNamespace.Length && 0 == string.CompareOrdinal(typeName, typeNamespace.Start, @namespace, 0, @namespace.Length);
|
||||
return @namespace.Length == namespaceTextSpan.Length && 0 == string.CompareOrdinal(typeName, namespaceTextSpan.Start, @namespace, 0, @namespace.Length);
|
||||
}
|
||||
|
||||
// Check if the given type is already in scope given the namespace of the current document.
|
||||
|
|
@ -368,13 +368,13 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
// Whereas `MyComponents.SomethingElse.OtherComponent` is not in scope.
|
||||
internal static bool IsTypeInScope(string typeName, string currentNamespace)
|
||||
{
|
||||
if (!TrySplitNamespaceAndType(typeName, out var typeNamespaceTextSpan, out var _) || typeNamespaceTextSpan.Length == 0)
|
||||
if (!TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _) || namespaceTextSpan.Length == 0)
|
||||
{
|
||||
// Either the typeName is not the full type name or this type is at the top level.
|
||||
return true;
|
||||
}
|
||||
|
||||
var typeNamespace = GetTextSpanContent(typeNamespaceTextSpan, typeName);
|
||||
var typeNamespace = GetTextSpanContent(namespaceTextSpan, typeName);
|
||||
var typeNamespaceSegments = typeNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
var currentNamespaceSegments = currentNamespace.Split(NamespaceSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (typeNamespaceSegments.Length > currentNamespaceSegments.Length)
|
||||
|
|
@ -402,8 +402,8 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
// If this is a child content tag helper, we want to look at it's original type.
|
||||
// E.g, if the type name is `Test.__generated__MyComponent.ChildContent`, we want to look at `Test.__generated__MyComponent`.
|
||||
TrySplitNamespaceAndType(typeName, out var typeNameTextSpan, out var _);
|
||||
typeName = GetTextSpanContent(typeNameTextSpan, typeName);
|
||||
TrySplitNamespaceAndType(typeName, out var namespaceTextSpan, out var _);
|
||||
typeName = GetTextSpanContent(namespaceTextSpan, typeName);
|
||||
}
|
||||
if (!TrySplitNamespaceAndType(typeName, out var _, out var classNameTextSpan))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
@page "/BlazorServerTagHelpers"
|
||||
|
||||
|
||||
@using blazorserver_clean.Data
|
||||
@inject WeatherForecastService ForecastService
|
||||
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p>Current count: @currentCount</p>
|
||||
|
||||
@{var someVariable = true;} @someVariable
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
<button class="btn btn-primary" @onkeypress:preventDefault @onclick="IncrementCount">Click me</button>
|
||||
|
||||
|
||||
<h1>Weather forecast</h1>
|
||||
|
||||
<p>This component demonstrates fetching data from a service.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Temp. (C)</th>
|
||||
<th>Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[] forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
|
||||
}
|
||||
}
|
||||
|
||||
<div class="top-row pl-4 navbar navbar-dark">
|
||||
<a class="navbar-brand" href="">blazorserver_clean</a>
|
||||
<button class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="counter">
|
||||
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="fetchdata">
|
||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,15 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
|
||||
<None Include="MSN.cshtml" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="taghelpers.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Include="BlazorServerTagHelpers.razor" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Compile Include="$(SharedSourceRoot)RazorShared\TagHelperDescriptorJsonConverter.cs">
|
||||
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="$(SharedSourceRoot)RazorShared\RazorDiagnosticJsonConverter.cs">
|
||||
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Razor.Serialization;
|
||||
using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using static Microsoft.AspNetCore.Razor.Language.DefaultRazorTagHelperBinderPhase;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Performance
|
||||
{
|
||||
public class RazorTagHelperParsingBenchmark
|
||||
{
|
||||
public RazorTagHelperParsingBenchmark()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current != null && !File.Exists(Path.Combine(current.FullName, "taghelpers.json")))
|
||||
{
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
var root = current;
|
||||
|
||||
var tagHelpers = ReadTagHelpers(Path.Combine(root.FullName, "taghelpers.json"));
|
||||
var blazorServerTagHelpersFilePath = Path.Combine(root.FullName, "BlazorServerTagHelpers.razor");
|
||||
|
||||
var fileSystem = RazorProjectFileSystem.Create(root.FullName);
|
||||
ProjectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, b => RazorExtensions.Register(b));
|
||||
BlazorServerTagHelpersDemoFile = fileSystem.GetItem(Path.Combine(blazorServerTagHelpersFilePath), FileKinds.Legacy);
|
||||
|
||||
ComponentDirectiveVisitor = new ComponentDirectiveVisitor(blazorServerTagHelpersFilePath, tagHelpers, currentNamespace: null);
|
||||
var codeDocument = ProjectEngine.ProcessDesignTime(BlazorServerTagHelpersDemoFile);
|
||||
SyntaxTree = codeDocument.GetSyntaxTree();
|
||||
}
|
||||
|
||||
private RazorProjectEngine ProjectEngine { get; }
|
||||
private RazorProjectItem BlazorServerTagHelpersDemoFile { get; }
|
||||
private ComponentDirectiveVisitor ComponentDirectiveVisitor { get; }
|
||||
private RazorSyntaxTree SyntaxTree { get; }
|
||||
|
||||
[Benchmark(Description = "TagHelper Design Time Processing")]
|
||||
public void TagHelper_ProcessDesignTime()
|
||||
{
|
||||
_ = ProjectEngine.ProcessDesignTime(BlazorServerTagHelpersDemoFile);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "TagHelper Component Directive Parsing")]
|
||||
public void TagHelper_ComponentDirectiveVisitor()
|
||||
{
|
||||
ComponentDirectiveVisitor.Visit(SyntaxTree);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<TagHelperDescriptor> ReadTagHelpers(string filePath)
|
||||
{
|
||||
var serializer = new JsonSerializer();
|
||||
serializer.Converters.Add(new RazorDiagnosticJsonConverter());
|
||||
serializer.Converters.Add(new TagHelperDescriptorJsonConverter());
|
||||
|
||||
using (var reader = new JsonTextReader(File.OpenText(filePath)))
|
||||
{
|
||||
return serializer.Deserialize<IReadOnlyList<TagHelperDescriptor>>(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue