Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit a04dd48

Browse files
m0sarynowak
authored andcommitted
Benchmarks: add Razor rendering benchmarks (#8765)
1 parent a2c8537 commit a04dd48

14 files changed

+357
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using Microsoft.AspNetCore.Html;
3+
using Microsoft.AspNetCore.Mvc.Razor;
4+
5+
public static class HelperExtensions
6+
{
7+
public static Func<T1, IHtmlContent> Helper<T1>(
8+
this RazorPageBase page,
9+
Func<T1, Func<object, IHtmlContent>> helper
10+
) => p1 => helper(p1)(null);
11+
12+
public static Func<T1, T2, IHtmlContent> Helper<T1, T2>(
13+
this RazorPageBase page,
14+
Func<T1, T2, Func<object, IHtmlContent>> helper
15+
) => (p1, p2) => helper(p1, p2)(null);
16+
17+
public static Func<T1, T2, T3, IHtmlContent> Helper<T1, T2, T3>(
18+
this RazorPageBase page,
19+
Func<T1, T2, T3, Func<object, IHtmlContent>> helper
20+
) => (p1, p2, p3) => helper(p1, p2, p3)(null);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<!-- Workaround https://github.com/dotnet/core-setup/issues/3726 -->
6+
<GenerateDependencyFile>false</GenerateDependencyFile>
7+
8+
<_EnableAllInclusiveRazorSdk>true</_EnableAllInclusiveRazorSdk>
9+
<RazorLangVersion>3.0</RazorLangVersion>
10+
<RazorDefaultConfiguration>MVC-3.0</RazorDefaultConfiguration>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
15+
<PackageReference Include="Microsoft.NET.Sdk.Razor" Version="$(MicrosoftNETSdkRazorPackageVersion)" PrivateAssets="All" />
16+
</ItemGroup>
17+
18+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Microsoft.AspNetCore.Mvc.Performance
2+
{
3+
public class ViewAssemblyMarker
4+
{
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello world
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@using System;
2+
@using Microsoft.AspNetCore.Html
3+
@model System.String
4+
@{Func<dynamic, IHtmlContent> SomeHelper = @<text>@{
5+
@item
6+
}</text>;}
7+
@for (var i = 0; i < 100; i++){@SomeHelper(Model + i.ToString())}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@using System;
2+
@using Microsoft.AspNetCore.Html
3+
@model System.String
4+
@{
5+
var SomeHelper = this.Helper((string s) => @<text>@item</text>);
6+
}
7+
@for (var i = 0; i < 100; i++)
8+
{
9+
@SomeHelper(Model + i.ToString())
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@using System;
2+
@using Microsoft.AspNetCore.Html
3+
@model System.String
4+
@for (var i = 0; i < 100; i++)
5+
{
6+
await Html.RenderPartialAsync("~/Views/HelperPartial_Partial.cshtml", Model + i.ToString());
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@using System;
2+
@using Microsoft.AspNetCore.Html
3+
@model System.String
4+
@for (var i = 0; i < 100; i++)
5+
{
6+
Html.RenderPartial("~/Views/HelperPartial_Partial.cshtml", Model + i.ToString());
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@using System;
2+
@using Microsoft.AspNetCore.Html
3+
@model System.String
4+
@addTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.PartialTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
5+
@for (var i = 0; i < 100; i++)
6+
{<partial name="~/Views/HelperPartial_Partial.cshtml" model="@(Model + i.ToString())" />}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@model System.String
2+
@Model
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@using System;
2+
@using Microsoft.AspNetCore.Html
3+
@model System.String
4+
@{Func<string, IHtmlContent> SomeHelper = @<text>@{
5+
@item
6+
}</text>;}
7+
@for (var i = 0; i < 100; i++){@SomeHelper(Model + i.ToString())}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.Diagnostics;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Runtime.CompilerServices;
11+
using System.Text;
12+
using System.Text.Encodings.Web;
13+
using System.Threading.Tasks;
14+
using BenchmarkDotNet.Attributes;
15+
using Microsoft.AspNetCore.Hosting;
16+
using Microsoft.AspNetCore.Html;
17+
using Microsoft.AspNetCore.Http;
18+
using Microsoft.AspNetCore.Mvc.Abstractions;
19+
using Microsoft.AspNetCore.Mvc.ApplicationParts;
20+
using Microsoft.AspNetCore.Mvc.Razor;
21+
using Microsoft.AspNetCore.Mvc.ViewEngines;
22+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
23+
using Microsoft.AspNetCore.Razor.Hosting;
24+
using Microsoft.AspNetCore.Routing;
25+
using Microsoft.CodeAnalysis;
26+
using Microsoft.Extensions.DependencyInjection;
27+
using Microsoft.Extensions.DependencyInjection.Extensions;
28+
using Microsoft.Extensions.FileProviders;
29+
using Microsoft.Extensions.Logging;
30+
using Microsoft.Extensions.ObjectPool;
31+
32+
namespace Microsoft.AspNetCore.Mvc.Performance
33+
{
34+
public class HelperPerformanceBenchmark : RuntimePerformanceBenchmarkBase
35+
{
36+
private Random _rand = new Random();
37+
public HelperPerformanceBenchmark() : base(
38+
"~/Views/HelperTyped.cshtml",
39+
"~/Views/HelperDynamic.cshtml",
40+
"~/Views/HelperPartialSync.cshtml",
41+
"~/Views/HelperPartialAsync.cshtml",
42+
"~/Views/HelperExtensions.cshtml",
43+
"~/Views/HelperPartialTagHelper.cshtml")
44+
{
45+
}
46+
47+
protected override object Model => _rand.Next().ToString();
48+
}
49+
}

benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99

1010
<ItemGroup>
1111
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
12+
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Performance.Views\Microsoft.AspNetCore.Mvc.Performance.Views.csproj" />
13+
1214
</ItemGroup>
1315

1416
<ItemGroup>
1517
<PackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
1618
<PackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" />
19+
20+
<PackageReference Include="Microsoft.AspNetCore.Razor.Runtime" Version="$(MicrosoftAspNetCoreRazorRuntimePackageVersion)" />
1721
</ItemGroup>
1822

1923
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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.Diagnostics;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using System.Runtime.CompilerServices;
11+
using System.Text;
12+
using System.Text.Encodings.Web;
13+
using System.Threading.Tasks;
14+
using BenchmarkDotNet.Attributes;
15+
using Microsoft.AspNetCore.Hosting;
16+
using Microsoft.AspNetCore.Html;
17+
using Microsoft.AspNetCore.Http;
18+
using Microsoft.AspNetCore.Mvc.Abstractions;
19+
using Microsoft.AspNetCore.Mvc.ApplicationParts;
20+
using Microsoft.AspNetCore.Mvc.Infrastructure;
21+
using Microsoft.AspNetCore.Mvc.ModelBinding;
22+
using Microsoft.AspNetCore.Mvc.Razor;
23+
using Microsoft.AspNetCore.Mvc.Rendering;
24+
using Microsoft.AspNetCore.Mvc.ViewEngines;
25+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
26+
using Microsoft.AspNetCore.Razor.Hosting;
27+
using Microsoft.AspNetCore.Routing;
28+
using Microsoft.CodeAnalysis;
29+
using Microsoft.Extensions.DependencyInjection;
30+
using Microsoft.Extensions.DependencyInjection.Extensions;
31+
using Microsoft.Extensions.FileProviders;
32+
using Microsoft.Extensions.Logging;
33+
using Microsoft.Extensions.ObjectPool;
34+
using Microsoft.Extensions.Options;
35+
36+
namespace Microsoft.AspNetCore.Mvc.Performance
37+
{
38+
public class RuntimePerformanceBenchmarkBase
39+
{
40+
private class NullLoggerFactory : ILoggerFactory, ILogger
41+
{
42+
void ILoggerFactory.AddProvider(ILoggerProvider provider) {}
43+
ILogger ILoggerFactory.CreateLogger(string categoryName) => this;
44+
void IDisposable.Dispose() {}
45+
IDisposable ILogger.BeginScope<TState>(TState state) => null;
46+
bool ILogger.IsEnabled(LogLevel logLevel) => false;
47+
void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) {}
48+
}
49+
50+
private class BenchmarkViewExecutor : ViewExecutor
51+
{
52+
public BenchmarkViewExecutor(IOptions<MvcViewOptions> viewOptions, IHttpResponseStreamWriterFactory writerFactory, ICompositeViewEngine viewEngine, ITempDataDictionaryFactory tempDataFactory, DiagnosticListener diagnosticListener, IModelMetadataProvider modelMetadataProvider)
53+
: base(viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticListener, modelMetadataProvider)
54+
{
55+
}
56+
57+
public StringBuilder StringBuilder { get; } = new StringBuilder();
58+
59+
public override async Task ExecuteAsync(
60+
ActionContext actionContext,
61+
IView view,
62+
ViewDataDictionary viewData,
63+
ITempDataDictionary tempData,
64+
string contentType,
65+
int? statusCode)
66+
{
67+
using (var stringWriter = new StringWriter(StringBuilder))
68+
{
69+
var viewContext = new ViewContext(
70+
actionContext,
71+
view,
72+
viewData,
73+
tempData,
74+
stringWriter,
75+
ViewOptions.HtmlHelperOptions);
76+
await ExecuteAsync(viewContext, contentType, statusCode);
77+
await stringWriter.FlushAsync();
78+
}
79+
80+
}
81+
}
82+
83+
84+
private class BenchmarkHostingEnvironment : IHostingEnvironment
85+
{
86+
public BenchmarkHostingEnvironment()
87+
{
88+
ApplicationName = typeof(ViewAssemblyMarker).Assembly.FullName;
89+
WebRootFileProvider = new NullFileProvider();
90+
ContentRootFileProvider = new NullFileProvider();
91+
ContentRootPath = AppContext.BaseDirectory;
92+
WebRootPath = AppContext.BaseDirectory;
93+
}
94+
95+
public string EnvironmentName { get; set; }
96+
public string ApplicationName { get; set; }
97+
public string WebRootPath { get; set; }
98+
public IFileProvider WebRootFileProvider { get; set; }
99+
public string ContentRootPath { get; set; }
100+
public IFileProvider ContentRootFileProvider { get; set; }
101+
}
102+
103+
protected RuntimePerformanceBenchmarkBase(params string[] viewPaths)
104+
{
105+
ViewPaths = viewPaths;
106+
}
107+
108+
public virtual string[] ViewPaths { get; private set; }
109+
110+
[ParamsSource(nameof(ViewPaths))]
111+
public string ViewPath;
112+
113+
protected IView View;
114+
115+
private ServiceProvider _serviceProvider;
116+
private RouteData _routeData;
117+
private ActionDescriptor _actionDescriptor;
118+
private IServiceScope _requestScope;
119+
private ICompositeViewEngine _viewEngine;
120+
private BenchmarkViewExecutor _executor;
121+
private ViewEngineResult _viewEngineResult;
122+
private ActionContext _actionContext;
123+
private ViewDataDictionary _viewDataDictionary;
124+
private ITempDataDictionaryFactory _tempDataDictionaryFactory;
125+
private ITempDataDictionary _tempData;
126+
127+
// runs once for every Document value
128+
[GlobalSetup]
129+
public void GlobalSetup()
130+
{
131+
var loader = new RazorCompiledItemLoader();
132+
var viewsDll = Path.ChangeExtension(typeof(ViewAssemblyMarker).Assembly.Location, "Views.dll");
133+
var viewsAssembly = Assembly.Load(File.ReadAllBytes(viewsDll));
134+
var services = new ServiceCollection();
135+
var listener = new DiagnosticListener(GetType().Assembly.FullName);
136+
var partManager = new ApplicationPartManager();
137+
partManager.ApplicationParts.Add(CompiledRazorAssemblyApplicationPartFactory.GetDefaultApplicationParts(viewsAssembly).Single());
138+
var builder = services
139+
.AddSingleton<ILoggerFactory, NullLoggerFactory>()
140+
.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
141+
.AddSingleton<DiagnosticSource>(listener)
142+
.AddSingleton(listener)
143+
.AddSingleton<IHostingEnvironment, BenchmarkHostingEnvironment>()
144+
.AddSingleton<ApplicationPartManager>(partManager)
145+
.AddScoped<BenchmarkViewExecutor>()
146+
.AddMvc();
147+
148+
_serviceProvider = services.BuildServiceProvider();
149+
_routeData = new RouteData();
150+
_actionDescriptor = new ActionDescriptor();
151+
_tempDataDictionaryFactory = _serviceProvider.GetRequiredService<ITempDataDictionaryFactory>();
152+
_viewEngine = _serviceProvider.GetRequiredService<ICompositeViewEngine>();
153+
}
154+
155+
[GlobalCleanup]
156+
public void GlobalCleanup()
157+
{
158+
_serviceProvider.Dispose();
159+
}
160+
161+
[IterationSetup]
162+
public virtual void IterationSetup()
163+
{
164+
_requestScope = _serviceProvider.CreateScope();
165+
166+
_viewEngineResult = _viewEngine.GetView(null, ViewPath, true);
167+
_viewEngineResult.EnsureSuccessful(null);
168+
169+
_actionContext = new ActionContext(
170+
new DefaultHttpContext()
171+
{
172+
RequestServices = _requestScope.ServiceProvider
173+
},
174+
_routeData,
175+
_actionDescriptor);
176+
177+
_tempData = _tempDataDictionaryFactory.GetTempData(_actionContext.HttpContext);
178+
179+
_viewDataDictionary = new ViewDataDictionary(
180+
_requestScope.ServiceProvider.GetRequiredService<IModelMetadataProvider>(),
181+
_actionContext.ModelState);
182+
_viewDataDictionary.Model = Model;
183+
184+
_executor = _requestScope.ServiceProvider.GetRequiredService<BenchmarkViewExecutor>();
185+
}
186+
187+
[IterationCleanup]
188+
public virtual void IterationCleanup()
189+
{
190+
if (_viewEngineResult.View is IDisposable d)
191+
{
192+
d.Dispose();
193+
}
194+
_requestScope.Dispose();
195+
}
196+
197+
protected virtual object Model { get; } = null;
198+
199+
[Benchmark]
200+
public async Task<string> RenderView()
201+
{
202+
await _executor.ExecuteAsync(
203+
_actionContext,
204+
_viewEngineResult.View,
205+
_viewDataDictionary,
206+
_tempData,
207+
"text/html",
208+
200);
209+
return _executor.StringBuilder.ToString();
210+
}
211+
}
212+
}

0 commit comments

Comments
 (0)