From a04dd4877cdf6fe23724db763461d9d1a108ebc9 Mon Sep 17 00:00:00 2001 From: Samo Prelog Date: Tue, 27 Nov 2018 17:14:48 +0100 Subject: [PATCH] Benchmarks: add Razor rendering benchmarks (#8765) --- .../HelperExtensions.cs | 21 ++ ...ft.AspNetCore.Mvc.Performance.Views.csproj | 18 ++ .../ViewAssemblyMarker.cs | 6 + .../Views/HelloWorld.cshtml | 1 + .../Views/HelperDynamic.cshtml | 7 + .../Views/HelperExtensions.cshtml | 10 + .../Views/HelperPartialAsync.cshtml | 7 + .../Views/HelperPartialSync.cshtml | 7 + .../Views/HelperPartialTagHelper.cshtml | 6 + .../Views/HelperPartial_Partial.cshtml | 2 + .../Views/HelperTyped.cshtml | 7 + .../HelperPerformanceBenchmark.cs | 49 ++++ ...icrosoft.AspNetCore.Mvc.Performance.csproj | 4 + .../RuntimePerformanceBenchmarkBase.cs | 212 ++++++++++++++++++ 14 files changed, 357 insertions(+) create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/HelperExtensions.cs create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Microsoft.AspNetCore.Mvc.Performance.Views.csproj create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/ViewAssemblyMarker.cs create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelloWorld.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperDynamic.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperExtensions.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialAsync.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialSync.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialTagHelper.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartial_Partial.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperTyped.cshtml create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance/HelperPerformanceBenchmark.cs create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance/RuntimePerformanceBenchmarkBase.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/HelperExtensions.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/HelperExtensions.cs new file mode 100644 index 0000000000..460ffef86f --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/HelperExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Razor; + +public static class HelperExtensions +{ + public static Func Helper( + this RazorPageBase page, + Func> helper + ) => p1 => helper(p1)(null); + + public static Func Helper( + this RazorPageBase page, + Func> helper + ) => (p1, p2) => helper(p1, p2)(null); + + public static Func Helper( + this RazorPageBase page, + Func> helper + ) => (p1, p2, p3) => helper(p1, p2, p3)(null); +} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Microsoft.AspNetCore.Mvc.Performance.Views.csproj b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Microsoft.AspNetCore.Mvc.Performance.Views.csproj new file mode 100644 index 0000000000..ad01f2e69e --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Microsoft.AspNetCore.Mvc.Performance.Views.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.0 + + false + + <_EnableAllInclusiveRazorSdk>true + 3.0 + MVC-3.0 + + + + + + + + diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/ViewAssemblyMarker.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/ViewAssemblyMarker.cs new file mode 100644 index 0000000000..bf2dfcd02d --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/ViewAssemblyMarker.cs @@ -0,0 +1,6 @@ +namespace Microsoft.AspNetCore.Mvc.Performance +{ + public class ViewAssemblyMarker + { + } +} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelloWorld.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelloWorld.cshtml new file mode 100644 index 0000000000..70c379b63f --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelloWorld.cshtml @@ -0,0 +1 @@ +Hello world \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperDynamic.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperDynamic.cshtml new file mode 100644 index 0000000000..8a476f9847 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperDynamic.cshtml @@ -0,0 +1,7 @@ +@using System; +@using Microsoft.AspNetCore.Html +@model System.String +@{Func SomeHelper = @@{ +@item +};} +@for (var i = 0; i < 100; i++){@SomeHelper(Model + i.ToString())} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperExtensions.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperExtensions.cshtml new file mode 100644 index 0000000000..583a755d1b --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperExtensions.cshtml @@ -0,0 +1,10 @@ +@using System; +@using Microsoft.AspNetCore.Html +@model System.String +@{ + var SomeHelper = this.Helper((string s) => @@item); +} +@for (var i = 0; i < 100; i++) +{ + @SomeHelper(Model + i.ToString()) +} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialAsync.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialAsync.cshtml new file mode 100644 index 0000000000..d8bc51b756 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialAsync.cshtml @@ -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()); +} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialSync.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialSync.cshtml new file mode 100644 index 0000000000..cd17862b20 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialSync.cshtml @@ -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()); +} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialTagHelper.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialTagHelper.cshtml new file mode 100644 index 0000000000..dc76d33bf2 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartialTagHelper.cshtml @@ -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++) +{} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartial_Partial.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartial_Partial.cshtml new file mode 100644 index 0000000000..da3f4178ae --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperPartial_Partial.cshtml @@ -0,0 +1,2 @@ +@model System.String +@Model \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperTyped.cshtml b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperTyped.cshtml new file mode 100644 index 0000000000..4eec71bbbf --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance.Views/Views/HelperTyped.cshtml @@ -0,0 +1,7 @@ +@using System; +@using Microsoft.AspNetCore.Html +@model System.String +@{Func SomeHelper = @@{ +@item +};} +@for (var i = 0; i < 100; i++){@SomeHelper(Model + i.ToString())} \ No newline at end of file diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/HelperPerformanceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/HelperPerformanceBenchmark.cs new file mode 100644 index 0000000000..572a11a73b --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/HelperPerformanceBenchmark.cs @@ -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(); + } +} diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj index 512b155184..c4cbacc80a 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj @@ -9,11 +9,15 @@ + + + + diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/RuntimePerformanceBenchmarkBase.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/RuntimePerformanceBenchmarkBase.cs new file mode 100644 index 0000000000..a763cba861 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/RuntimePerformanceBenchmarkBase.cs @@ -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 state) => null; + bool ILogger.IsEnabled(LogLevel logLevel) => false; + void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) {} + } + + private class BenchmarkViewExecutor : ViewExecutor + { + public BenchmarkViewExecutor(IOptions 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() + .AddSingleton() + .AddSingleton(listener) + .AddSingleton(listener) + .AddSingleton() + .AddSingleton(partManager) + .AddScoped() + .AddMvc(); + + _serviceProvider = services.BuildServiceProvider(); + _routeData = new RouteData(); + _actionDescriptor = new ActionDescriptor(); + _tempDataDictionaryFactory = _serviceProvider.GetRequiredService(); + _viewEngine = _serviceProvider.GetRequiredService(); + } + + [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(), + _actionContext.ModelState); + _viewDataDictionary.Model = Model; + + _executor = _requestScope.ServiceProvider.GetRequiredService(); + } + + [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 RenderView() + { + await _executor.ExecuteAsync( + _actionContext, + _viewEngineResult.View, + _viewDataDictionary, + _tempData, + "text/html", + 200); + return _executor.StringBuilder.ToString(); + } + } +}