Benchmarks: add Razor rendering benchmarks (#8765)
This commit is contained in:
parent
a2c8537dd8
commit
a04dd4877c
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
|
||||||
|
public static class HelperExtensions
|
||||||
|
{
|
||||||
|
public static Func<T1, IHtmlContent> Helper<T1>(
|
||||||
|
this RazorPageBase page,
|
||||||
|
Func<T1, Func<object, IHtmlContent>> helper
|
||||||
|
) => p1 => helper(p1)(null);
|
||||||
|
|
||||||
|
public static Func<T1, T2, IHtmlContent> Helper<T1, T2>(
|
||||||
|
this RazorPageBase page,
|
||||||
|
Func<T1, T2, Func<object, IHtmlContent>> helper
|
||||||
|
) => (p1, p2) => helper(p1, p2)(null);
|
||||||
|
|
||||||
|
public static Func<T1, T2, T3, IHtmlContent> Helper<T1, T2, T3>(
|
||||||
|
this RazorPageBase page,
|
||||||
|
Func<T1, T2, T3, Func<object, IHtmlContent>> helper
|
||||||
|
) => (p1, p2, p3) => helper(p1, p2, p3)(null);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<!-- Workaround https://github.com/dotnet/core-setup/issues/3726 -->
|
||||||
|
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||||
|
|
||||||
|
<_EnableAllInclusiveRazorSdk>true</_EnableAllInclusiveRazorSdk>
|
||||||
|
<RazorLangVersion>3.0</RazorLangVersion>
|
||||||
|
<RazorDefaultConfiguration>MVC-3.0</RazorDefaultConfiguration>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
|
{
|
||||||
|
public class ViewAssemblyMarker
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Hello world
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
@using System;
|
||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@model System.String
|
||||||
|
@{Func<dynamic, IHtmlContent> SomeHelper = @<text>@{
|
||||||
|
@item
|
||||||
|
}</text>;}
|
||||||
|
@for (var i = 0; i < 100; i++){@SomeHelper(Model + i.ToString())}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
@using System;
|
||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@model System.String
|
||||||
|
@{
|
||||||
|
var SomeHelper = this.Helper((string s) => @<text>@item</text>);
|
||||||
|
}
|
||||||
|
@for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
@SomeHelper(Model + i.ToString())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
@using System;
|
||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@model System.String
|
||||||
|
@for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
await Html.RenderPartialAsync("~/Views/HelperPartial_Partial.cshtml", Model + i.ToString());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
@using System;
|
||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@model System.String
|
||||||
|
@for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
Html.RenderPartial("~/Views/HelperPartial_Partial.cshtml", Model + i.ToString());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
@using System;
|
||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@model System.String
|
||||||
|
@addTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.PartialTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@for (var i = 0; i < 100; i++)
|
||||||
|
{<partial name="~/Views/HelperPartial_Partial.cshtml" model="@(Model + i.ToString())" />}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@model System.String
|
||||||
|
@Model
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
@using System;
|
||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@model System.String
|
||||||
|
@{Func<string, IHtmlContent> SomeHelper = @<text>@{
|
||||||
|
@item
|
||||||
|
}</text>;}
|
||||||
|
@for (var i = 0; i < 100; i++){@SomeHelper(Model + i.ToString())}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
|
{
|
||||||
|
public class HelperPerformanceBenchmark : RuntimePerformanceBenchmarkBase
|
||||||
|
{
|
||||||
|
private Random _rand = new Random();
|
||||||
|
public HelperPerformanceBenchmark() : base(
|
||||||
|
"~/Views/HelperTyped.cshtml",
|
||||||
|
"~/Views/HelperDynamic.cshtml",
|
||||||
|
"~/Views/HelperPartialSync.cshtml",
|
||||||
|
"~/Views/HelperPartialAsync.cshtml",
|
||||||
|
"~/Views/HelperExtensions.cshtml",
|
||||||
|
"~/Views/HelperPartialTagHelper.cshtml")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override object Model => _rand.Next().ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,11 +9,15 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
|
||||||
|
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Performance.Views\Microsoft.AspNetCore.Mvc.Performance.Views.csproj" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Razor.Runtime" Version="$(MicrosoftAspNetCoreRazorRuntimePackageVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.AspNetCore.Razor.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.ObjectPool;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Performance
|
||||||
|
{
|
||||||
|
public class RuntimePerformanceBenchmarkBase
|
||||||
|
{
|
||||||
|
private class NullLoggerFactory : ILoggerFactory, ILogger
|
||||||
|
{
|
||||||
|
void ILoggerFactory.AddProvider(ILoggerProvider provider) {}
|
||||||
|
ILogger ILoggerFactory.CreateLogger(string categoryName) => this;
|
||||||
|
void IDisposable.Dispose() {}
|
||||||
|
IDisposable ILogger.BeginScope<TState>(TState state) => null;
|
||||||
|
bool ILogger.IsEnabled(LogLevel logLevel) => false;
|
||||||
|
void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BenchmarkViewExecutor : ViewExecutor
|
||||||
|
{
|
||||||
|
public BenchmarkViewExecutor(IOptions<MvcViewOptions> viewOptions, IHttpResponseStreamWriterFactory writerFactory, ICompositeViewEngine viewEngine, ITempDataDictionaryFactory tempDataFactory, DiagnosticListener diagnosticListener, IModelMetadataProvider modelMetadataProvider)
|
||||||
|
: base(viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticListener, modelMetadataProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuilder StringBuilder { get; } = new StringBuilder();
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync(
|
||||||
|
ActionContext actionContext,
|
||||||
|
IView view,
|
||||||
|
ViewDataDictionary viewData,
|
||||||
|
ITempDataDictionary tempData,
|
||||||
|
string contentType,
|
||||||
|
int? statusCode)
|
||||||
|
{
|
||||||
|
using (var stringWriter = new StringWriter(StringBuilder))
|
||||||
|
{
|
||||||
|
var viewContext = new ViewContext(
|
||||||
|
actionContext,
|
||||||
|
view,
|
||||||
|
viewData,
|
||||||
|
tempData,
|
||||||
|
stringWriter,
|
||||||
|
ViewOptions.HtmlHelperOptions);
|
||||||
|
await ExecuteAsync(viewContext, contentType, statusCode);
|
||||||
|
await stringWriter.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class BenchmarkHostingEnvironment : IHostingEnvironment
|
||||||
|
{
|
||||||
|
public BenchmarkHostingEnvironment()
|
||||||
|
{
|
||||||
|
ApplicationName = typeof(ViewAssemblyMarker).Assembly.FullName;
|
||||||
|
WebRootFileProvider = new NullFileProvider();
|
||||||
|
ContentRootFileProvider = new NullFileProvider();
|
||||||
|
ContentRootPath = AppContext.BaseDirectory;
|
||||||
|
WebRootPath = AppContext.BaseDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EnvironmentName { get; set; }
|
||||||
|
public string ApplicationName { get; set; }
|
||||||
|
public string WebRootPath { get; set; }
|
||||||
|
public IFileProvider WebRootFileProvider { get; set; }
|
||||||
|
public string ContentRootPath { get; set; }
|
||||||
|
public IFileProvider ContentRootFileProvider { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RuntimePerformanceBenchmarkBase(params string[] viewPaths)
|
||||||
|
{
|
||||||
|
ViewPaths = viewPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string[] ViewPaths { get; private set; }
|
||||||
|
|
||||||
|
[ParamsSource(nameof(ViewPaths))]
|
||||||
|
public string ViewPath;
|
||||||
|
|
||||||
|
protected IView View;
|
||||||
|
|
||||||
|
private ServiceProvider _serviceProvider;
|
||||||
|
private RouteData _routeData;
|
||||||
|
private ActionDescriptor _actionDescriptor;
|
||||||
|
private IServiceScope _requestScope;
|
||||||
|
private ICompositeViewEngine _viewEngine;
|
||||||
|
private BenchmarkViewExecutor _executor;
|
||||||
|
private ViewEngineResult _viewEngineResult;
|
||||||
|
private ActionContext _actionContext;
|
||||||
|
private ViewDataDictionary _viewDataDictionary;
|
||||||
|
private ITempDataDictionaryFactory _tempDataDictionaryFactory;
|
||||||
|
private ITempDataDictionary _tempData;
|
||||||
|
|
||||||
|
// runs once for every Document value
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
var loader = new RazorCompiledItemLoader();
|
||||||
|
var viewsDll = Path.ChangeExtension(typeof(ViewAssemblyMarker).Assembly.Location, "Views.dll");
|
||||||
|
var viewsAssembly = Assembly.Load(File.ReadAllBytes(viewsDll));
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
var listener = new DiagnosticListener(GetType().Assembly.FullName);
|
||||||
|
var partManager = new ApplicationPartManager();
|
||||||
|
partManager.ApplicationParts.Add(CompiledRazorAssemblyApplicationPartFactory.GetDefaultApplicationParts(viewsAssembly).Single());
|
||||||
|
var builder = services
|
||||||
|
.AddSingleton<ILoggerFactory, NullLoggerFactory>()
|
||||||
|
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
|
||||||
|
.AddSingleton<DiagnosticSource>(listener)
|
||||||
|
.AddSingleton(listener)
|
||||||
|
.AddSingleton<IHostingEnvironment, BenchmarkHostingEnvironment>()
|
||||||
|
.AddSingleton<ApplicationPartManager>(partManager)
|
||||||
|
.AddScoped<BenchmarkViewExecutor>()
|
||||||
|
.AddMvc();
|
||||||
|
|
||||||
|
_serviceProvider = services.BuildServiceProvider();
|
||||||
|
_routeData = new RouteData();
|
||||||
|
_actionDescriptor = new ActionDescriptor();
|
||||||
|
_tempDataDictionaryFactory = _serviceProvider.GetRequiredService<ITempDataDictionaryFactory>();
|
||||||
|
_viewEngine = _serviceProvider.GetRequiredService<ICompositeViewEngine>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public void GlobalCleanup()
|
||||||
|
{
|
||||||
|
_serviceProvider.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[IterationSetup]
|
||||||
|
public virtual void IterationSetup()
|
||||||
|
{
|
||||||
|
_requestScope = _serviceProvider.CreateScope();
|
||||||
|
|
||||||
|
_viewEngineResult = _viewEngine.GetView(null, ViewPath, true);
|
||||||
|
_viewEngineResult.EnsureSuccessful(null);
|
||||||
|
|
||||||
|
_actionContext = new ActionContext(
|
||||||
|
new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = _requestScope.ServiceProvider
|
||||||
|
},
|
||||||
|
_routeData,
|
||||||
|
_actionDescriptor);
|
||||||
|
|
||||||
|
_tempData = _tempDataDictionaryFactory.GetTempData(_actionContext.HttpContext);
|
||||||
|
|
||||||
|
_viewDataDictionary = new ViewDataDictionary(
|
||||||
|
_requestScope.ServiceProvider.GetRequiredService<IModelMetadataProvider>(),
|
||||||
|
_actionContext.ModelState);
|
||||||
|
_viewDataDictionary.Model = Model;
|
||||||
|
|
||||||
|
_executor = _requestScope.ServiceProvider.GetRequiredService<BenchmarkViewExecutor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[IterationCleanup]
|
||||||
|
public virtual void IterationCleanup()
|
||||||
|
{
|
||||||
|
if (_viewEngineResult.View is IDisposable d)
|
||||||
|
{
|
||||||
|
d.Dispose();
|
||||||
|
}
|
||||||
|
_requestScope.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual object Model { get; } = null;
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task<string> RenderView()
|
||||||
|
{
|
||||||
|
await _executor.ExecuteAsync(
|
||||||
|
_actionContext,
|
||||||
|
_viewEngineResult.View,
|
||||||
|
_viewDataDictionary,
|
||||||
|
_tempData,
|
||||||
|
"text/html",
|
||||||
|
200);
|
||||||
|
return _executor.StringBuilder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue