Skip to content

Commit 1885af9

Browse files
Add console runner for measuring Blazor perf on desktop interpreter (#24469)
1 parent 0335ac6 commit 1885af9

File tree

12 files changed

+237
-43
lines changed

12 files changed

+237
-43
lines changed

AspNetCore.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropClient", "src\Grpc\t
14551455
EndProject
14561456
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "src\Grpc\test\testassets\InteropWebsite\InteropWebsite.csproj", "{19189670-E206-471D-94F8-7D3D545E5020}"
14571457
EndProject
1458+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.ConsoleHost", "src\Components\benchmarkapps\Wasm.Performance\ConsoleHost\Wasm.Performance.ConsoleHost.csproj", "{E9408723-E6A9-4715-B906-3B25B0238ABA}"
1459+
EndProject
14581460
Global
14591461
GlobalSection(SolutionConfigurationPlatforms) = preSolution
14601462
Debug|Any CPU = Debug|Any CPU
@@ -6889,6 +6891,18 @@ Global
68896891
{19189670-E206-471D-94F8-7D3D545E5020}.Release|x64.Build.0 = Release|Any CPU
68906892
{19189670-E206-471D-94F8-7D3D545E5020}.Release|x86.ActiveCfg = Release|Any CPU
68916893
{19189670-E206-471D-94F8-7D3D545E5020}.Release|x86.Build.0 = Release|Any CPU
6894+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
6895+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU
6896+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x64.ActiveCfg = Debug|Any CPU
6897+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x64.Build.0 = Debug|Any CPU
6898+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x86.ActiveCfg = Debug|Any CPU
6899+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x86.Build.0 = Debug|Any CPU
6900+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU
6901+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|Any CPU.Build.0 = Release|Any CPU
6902+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x64.ActiveCfg = Release|Any CPU
6903+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x64.Build.0 = Release|Any CPU
6904+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x86.ActiveCfg = Release|Any CPU
6905+
{E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x86.Build.0 = Release|Any CPU
68926906
EndGlobalSection
68936907
GlobalSection(SolutionProperties) = preSolution
68946908
HideSolutionNode = FALSE
@@ -7618,6 +7632,7 @@ Global
76187632
{00B2DD87-7E2A-4460-BE1B-5E18B1062B7F} = {E763DA15-8F4E-446C-99B8-309053C75598}
76197633
{C3A0F425-669F-46A8-893F-CF449A6DAE56} = {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F}
76207634
{19189670-E206-471D-94F8-7D3D545E5020} = {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F}
7635+
{E9408723-E6A9-4715-B906-3B25B0238ABA} = {6276A9A0-791B-49C1-AD8F-50AC47CDC196}
76217636
EndGlobalSection
76227637
GlobalSection(ExtensibilityGlobals) = postSolution
76237638
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

src/Components/ComponentsNoDeps.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"src\\Components\\WebAssembly\\Sdk\\integrationtests\\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj",
3838
"src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
3939
"src\\Components\\Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
40+
"src\\Components\\benchmarkapps\\Wasm.Performance\\ConsoleHost\\Wasm.Performance.ConsoleHost.csproj",
4041
"src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
4142
"src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
4243
"src\\Components\\test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.Runtime.ExceptionServices;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Components;
8+
using Microsoft.AspNetCore.Components.RenderTree;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Wasm.Performance.ConsoleHost
12+
{
13+
internal class ConsoleHostRenderer : Renderer
14+
{
15+
public ConsoleHostRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
16+
: base(serviceProvider, loggerFactory)
17+
{
18+
}
19+
20+
public override Dispatcher Dispatcher { get; } = new NullDispatcher();
21+
22+
protected override void HandleException(Exception exception)
23+
{
24+
ExceptionDispatchInfo.Capture(exception).Throw();
25+
}
26+
27+
protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
28+
{
29+
// ConsoleHost is only for profiling the .NET side of execution.
30+
// There isn't a real display to update.
31+
return Task.CompletedTask;
32+
}
33+
34+
// Expose some protected APIs publicly
35+
public new int AssignRootComponentId(IComponent component)
36+
=> base.AssignRootComponentId(component);
37+
38+
public new Task RenderRootComponentAsync(int componentId)
39+
=> base.RenderRootComponentAsync(componentId);
40+
}
41+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.Threading.Tasks;
6+
using Microsoft.AspNetCore.Components;
7+
8+
namespace Wasm.Performance.ConsoleHost
9+
{
10+
internal class NullDispatcher : Dispatcher
11+
{
12+
public override bool CheckAccess()
13+
=> true;
14+
15+
public override Task InvokeAsync(Action workItem)
16+
{
17+
workItem();
18+
return Task.CompletedTask;
19+
}
20+
21+
public override Task InvokeAsync(Func<Task> workItem)
22+
=> workItem();
23+
24+
public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
25+
=> Task.FromResult(workItem());
26+
27+
public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
28+
=> workItem();
29+
}
30+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 Microsoft.Extensions.CommandLineUtils;
5+
using Wasm.Performance.ConsoleHost.Scenarios;
6+
7+
namespace Wasm.Performance.ConsoleHost
8+
{
9+
internal class Program : CommandLineApplication
10+
{
11+
static void Main(string[] args)
12+
{
13+
new Program().Execute(args);
14+
}
15+
16+
public Program()
17+
{
18+
OnExecute(() =>
19+
{
20+
ShowHelp();
21+
return 1;
22+
});
23+
24+
Commands.Add(new GridScenario());
25+
}
26+
}
27+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.Threading.Tasks;
6+
using Microsoft.Extensions.CommandLineUtils;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Wasm.Performance.ConsoleHost.Scenarios
11+
{
12+
internal abstract class ComponentRenderingScenarioBase : CommandLineApplication
13+
{
14+
protected ComponentRenderingScenarioBase(string name)
15+
{
16+
Name = name;
17+
18+
var cyclesOption = new CommandOption("--cycles", CommandOptionType.SingleValue);
19+
Options.Add(cyclesOption);
20+
21+
OnExecute(() =>
22+
{
23+
var numCycles = cyclesOption.HasValue() ? int.Parse(cyclesOption.Value()) : 1;
24+
25+
var serviceCollection = new ServiceCollection();
26+
PopulateServiceCollection(serviceCollection);
27+
var serviceProvider = serviceCollection.BuildServiceProvider();
28+
29+
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
30+
var renderer = new ConsoleHostRenderer(serviceProvider, loggerFactory);
31+
32+
var startTime = DateTime.Now;
33+
ExecuteAsync(renderer, numCycles).Wait();
34+
35+
var duration = DateTime.Now - startTime;
36+
var durationPerCycle = (duration / numCycles).TotalMilliseconds;
37+
Console.WriteLine($"{Name}: {durationPerCycle:F1}ms per cycle (cycles tested: {numCycles})");
38+
39+
return 0;
40+
});
41+
}
42+
43+
protected virtual void PopulateServiceCollection(IServiceCollection serviceCollection)
44+
{
45+
serviceCollection.AddLogging();
46+
}
47+
48+
protected abstract Task ExecuteAsync(ConsoleHostRenderer renderer, int numCycles);
49+
}
50+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.Threading.Tasks;
6+
using Microsoft.Extensions.CommandLineUtils;
7+
using Wasm.Performance.TestApp.Pages;
8+
9+
namespace Wasm.Performance.ConsoleHost.Scenarios
10+
{
11+
internal class GridScenario : ComponentRenderingScenarioBase
12+
{
13+
readonly CommandOption _gridTypeOption = new CommandOption("--gridtype", CommandOptionType.SingleValue);
14+
15+
public GridScenario() : base("grid")
16+
{
17+
Options.Add(_gridTypeOption);
18+
}
19+
20+
protected override async Task ExecuteAsync(ConsoleHostRenderer renderer, int numCycles)
21+
{
22+
var gridType = _gridTypeOption.HasValue()
23+
? (GridRendering.RenderMode)Enum.Parse(typeof(GridRendering.RenderMode), _gridTypeOption.Value(), true)
24+
: GridRendering.RenderMode.FastGrid;
25+
26+
for (var i = 0; i < numCycles; i++)
27+
{
28+
var hostPage = new GridRendering { SelectedRenderMode = gridType };
29+
hostPage.Show();
30+
31+
var componentId = renderer.AssignRootComponentId(hostPage);
32+
await renderer.RenderRootComponentAsync(componentId);
33+
34+
hostPage.ChangePage();
35+
await renderer.RenderRootComponentAsync(componentId);
36+
}
37+
}
38+
}
39+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
6+
<IsPackable>false</IsPackable>
7+
<IsShipping>false</IsShipping>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
12+
<Compile Include="$(SharedSourceRoot)CommandLineUtils\**\*.cs" />
13+
</ItemGroup>
14+
15+
</Project>

src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public static class BenchmarkEvent
99
{
1010
public static void Send(IJSRuntime jsRuntime, string name)
1111
{
12-
((IJSInProcessRuntime)jsRuntime).Invoke<object>(
12+
// jsRuntime will be null if we're in an environment without any
13+
// JS runtime, e.g., the console runner
14+
((IJSInProcessRuntime)jsRuntime)?.Invoke<object>(
1315
"receiveBenchmarkEvent",
1416
name);
1517
}

src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
@page "/gridrendering"
1+
@page "/gridrendering"
22
@inject IJSRuntime JSRuntime
33
@using Wasm.Performance.TestApp.Shared.FastGrid
44

55
<h1>20 x 200 Grid</h1>
66

77
<fieldset>
8-
<select id="render-mode" @bind="selectedRenderMode">
8+
<select id="render-mode" @bind="SelectedRenderMode">
99
<option>@RenderMode.FastGrid</option>
1010
<option>@RenderMode.PlainTable</option>
1111
<option>@RenderMode.ComplexTable</option>
@@ -23,7 +23,7 @@
2323
{
2424
<p><em>(No data assigned)</em></p>
2525
}
26-
else if (selectedRenderMode == RenderMode.FastGrid)
26+
else if (SelectedRenderMode == RenderMode.FastGrid)
2727
{
2828
<p>FastGrid represents a minimal, optimized implementation of a grid.</p>
2929

@@ -50,23 +50,23 @@ else if (selectedRenderMode == RenderMode.FastGrid)
5050
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
5151
</Grid>
5252
}
53-
else if (selectedRenderMode == RenderMode.PlainTable)
53+
else if (SelectedRenderMode == RenderMode.PlainTable)
5454
{
5555
<p>PlainTable represents a minimal but not optimized implementation of a grid.</p>
5656

5757
<Wasm.Performance.TestApp.Shared.PlainTable.TableComponent Data="@forecasts" Columns="@Columns" />
5858
}
59-
else if (selectedRenderMode == RenderMode.ComplexTable)
59+
else if (SelectedRenderMode == RenderMode.ComplexTable)
6060
{
6161
<p>ComplexTable represents a maximal, not optimized implementation of a grid, using a wide range of Blazor features at once.</p>
6262

6363
<Wasm.Performance.TestApp.Shared.ComplexTable.TableComponent Data="@forecasts" Columns="@Columns" />
6464
}
6565

6666
@code {
67-
enum RenderMode { PlainTable, ComplexTable, FastGrid }
67+
public enum RenderMode { PlainTable, ComplexTable, FastGrid }
6868

69-
private RenderMode selectedRenderMode = RenderMode.FastGrid;
69+
public RenderMode SelectedRenderMode { get; set; } = RenderMode.FastGrid;
7070

7171
private WeatherForecast[] forecasts;
7272
public List<string> Columns { get; set; } = new List<string>
@@ -89,17 +89,17 @@ else if (selectedRenderMode == RenderMode.ComplexTable)
8989
TemperatureC = index,
9090
};
9191

92-
void Show()
92+
public void Show()
9393
{
9494
forecasts = staticSampleDataPage1;
9595
}
9696

97-
void Hide()
97+
public void Hide()
9898
{
9999
forecasts = null;
100100
}
101101

102-
void ChangePage()
102+
public void ChangePage()
103103
{
104104
forecasts = (forecasts == staticSampleDataPage1) ? staticSampleDataPage2 : staticSampleDataPage1;
105105
}
Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@using WeatherForecast = Pages.GridRendering.WeatherForecast
1+
@using WeatherForecast = Pages.GridRendering.WeatherForecast
22

33
<table class="table">
44
<thead>
@@ -13,7 +13,7 @@
1313
<CascadingValue Value="@this">
1414
<RowCollection Data="@Data"
1515
Columns="@Columns"
16-
OnClick="@RefreshComponent"></RowCollection>
16+
OnClick="@HandleClickEvent"></RowCollection>
1717
</CascadingValue>
1818
</tbody>
1919
</table>
@@ -26,21 +26,8 @@
2626
[Parameter]
2727
public List<string> Columns { get; set; }
2828

29-
DateTime t1;
30-
DateTime t2;
31-
Task RefreshComponent(int index)
29+
Task HandleClickEvent(int index)
3230
{
33-
t1 = DateTime.Now;
34-
StateHasChanged();
3531
return Task.CompletedTask;
3632
}
37-
protected override Task OnAfterRenderAsync(bool firstRender)
38-
{
39-
if (!firstRender)
40-
{
41-
t2 = DateTime.Now;
42-
Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds);
43-
}
44-
return base.OnAfterRenderAsync(firstRender);
45-
}
4633
}

0 commit comments

Comments
 (0)