Skip to content

Commit 1998a06

Browse files
Blazor grid performance scenarios (#23301)
* Add complex table benchmark * Add FastGrid scenario too * Make the two grids consistent with each other * Add scenario for PlainTable * Empty commit to trigger re-rerun on CI. Clicking retry doesn't seem to be working.
1 parent 38f9b9a commit 1998a06

File tree

14 files changed

+592
-1
lines changed

14 files changed

+592
-1
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
@page "/gridrendering"
2+
@inject IJSRuntime JSRuntime
3+
@using Wasm.Performance.TestApp.Shared.FastGrid
4+
5+
<h1>20 x 200 Grid</h1>
6+
7+
<fieldset>
8+
<select id="render-mode" @bind="selectedRenderMode">
9+
<option>@RenderMode.FastGrid</option>
10+
<option>@RenderMode.PlainTable</option>
11+
<option>@RenderMode.ComplexTable</option>
12+
</select>
13+
14+
<button id="show" @onclick="Show">Show</button>
15+
<button id="hide" @onclick="Hide">Hide</button>
16+
@if (forecasts != null)
17+
{
18+
<button id="change-page" @onclick="ChangePage">Switch pages</button>
19+
}
20+
</fieldset>
21+
22+
@if (forecasts == null)
23+
{
24+
<p><em>(No data assigned)</em></p>
25+
}
26+
else if (selectedRenderMode == RenderMode.FastGrid)
27+
{
28+
<p>FastGrid represents a minimal, optimized implementation of a grid.</p>
29+
30+
<Grid Data="@forecasts">
31+
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
32+
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
33+
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
34+
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
35+
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
36+
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
37+
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
38+
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
39+
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
40+
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
41+
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
42+
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
43+
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
44+
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
45+
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
46+
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
47+
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
48+
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
49+
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
50+
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
51+
</Grid>
52+
}
53+
else if (selectedRenderMode == RenderMode.PlainTable)
54+
{
55+
<p>PlainTable represents a minimal but not optimized implementation of a grid.</p>
56+
57+
<Wasm.Performance.TestApp.Shared.PlainTable.TableComponent Data="@forecasts" Columns="@Columns" />
58+
}
59+
else if (selectedRenderMode == RenderMode.ComplexTable)
60+
{
61+
<p>ComplexTable represents a maximal, not optimized implementation of a grid, using a wide range of Blazor features at once.</p>
62+
63+
<Wasm.Performance.TestApp.Shared.ComplexTable.TableComponent Data="@forecasts" Columns="@Columns" />
64+
}
65+
66+
@code {
67+
enum RenderMode { PlainTable, ComplexTable, FastGrid }
68+
69+
private RenderMode selectedRenderMode = RenderMode.FastGrid;
70+
71+
private WeatherForecast[] forecasts;
72+
public List<string> Columns { get; set; } = new List<string>
73+
{
74+
"Date", "TemperatureC", "TemperatureF", "Summary",
75+
"Date", "TemperatureC", "TemperatureF", "Summary",
76+
"Date", "TemperatureC", "TemperatureF", "Summary",
77+
"Date", "TemperatureC", "TemperatureF", "Summary",
78+
"Date", "TemperatureC", "TemperatureF", "Summary",
79+
};
80+
81+
private static string[] sampleSummaries = new[] { "Balmy", "Chilly", "Freezing", "Bracing" };
82+
private static WeatherForecast[] staticSampleDataPage1 = Enumerable.Range(0, 200).Select(CreateSampleDataItem).ToArray();
83+
private static WeatherForecast[] staticSampleDataPage2 = Enumerable.Range(200, 200).Select(CreateSampleDataItem).ToArray();
84+
85+
private static WeatherForecast CreateSampleDataItem(int index) => new WeatherForecast
86+
{
87+
Date = DateTime.Now.Date.AddDays(index),
88+
Summary = sampleSummaries[index % sampleSummaries.Length],
89+
TemperatureC = index,
90+
};
91+
92+
void Show()
93+
{
94+
forecasts = staticSampleDataPage1;
95+
}
96+
97+
void Hide()
98+
{
99+
forecasts = null;
100+
}
101+
102+
void ChangePage()
103+
{
104+
forecasts = (forecasts == staticSampleDataPage1) ? staticSampleDataPage2 : staticSampleDataPage1;
105+
}
106+
107+
protected override void OnAfterRender(bool firstRender)
108+
{
109+
BenchmarkEvent.Send(JSRuntime, "Finished rendering table");
110+
}
111+
112+
public class WeatherForecast
113+
{
114+
public DateTime Date { get; set; }
115+
116+
public int TemperatureC { get; set; }
117+
118+
public string Summary { get; set; }
119+
120+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
121+
}
122+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@using WeatherForecast = Pages.GridRendering.WeatherForecast
2+
3+
<td @attributes="@Attributes"
4+
@onclick="@(() => OnClick.Invoke(CellIndex))"
5+
>
6+
@switch (Field)
7+
{
8+
case "Date":
9+
@Item.Date.ToShortDateString()
10+
break;
11+
case "TemperatureC":
12+
@Item.TemperatureC
13+
break;
14+
case "TemperatureF":
15+
@Item.TemperatureF
16+
break;
17+
case "Summary":
18+
@Item.Summary
19+
break;
20+
}
21+
</td>
22+
23+
@code {
24+
[Parameter]
25+
public WeatherForecast Item { get; set; }
26+
27+
[CascadingParameter]
28+
public TableComponent ParentTable { get; set; }
29+
30+
[Parameter]
31+
public string Field { get; set; }
32+
33+
[Parameter]
34+
public int CellIndex { get; set; }
35+
36+
[Parameter]
37+
public int RowIndex { get; set; }
38+
39+
[Parameter]
40+
public bool Selected { get; set; }
41+
42+
[Parameter]
43+
public string FormatString { get; set; }
44+
45+
[Parameter]
46+
public Func<int, Task> OnClick { get; set; }
47+
48+
private protected Dictionary<string, object> Attributes
49+
{
50+
get
51+
{
52+
var attributes = new Dictionary<string, object>();
53+
54+
attributes["tabindex"] = CellIndex;
55+
56+
return attributes;
57+
}
58+
}
59+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@using WeatherForecast = Pages.GridRendering.WeatherForecast
2+
3+
<tr class="complex-table-row" style="@rowStyle">
4+
@foreach (var item in Columns)
5+
{
6+
<Cell Item="@Item"
7+
Field="@item"
8+
CellIndex="1"
9+
RowIndex="2"
10+
Selected="@isSelected"
11+
FormatString="foo"
12+
OnClick="@OnCellClick">
13+
</Cell>
14+
}
15+
</tr>
16+
17+
18+
@code {
19+
20+
private bool isSelected = false;
21+
22+
private string rowStyle => isSelected ? "background-color: lightblue;" : "";
23+
24+
[Parameter]
25+
public WeatherForecast Item { get; set; }
26+
27+
[Parameter]
28+
public List<string> Columns { get; set; }
29+
30+
[Parameter]
31+
public Func<int, Task> OnClick { get; set; }
32+
33+
Task OnCellClick(int args)
34+
{
35+
isSelected = !isSelected;
36+
37+
return OnClick.Invoke(args);
38+
}
39+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@using WeatherForecast = Pages.GridRendering.WeatherForecast
2+
3+
@foreach (var item in Data)
4+
{
5+
<Row Item="@item" Columns="@Columns"
6+
OnClick="@OnClick"></Row>
7+
}
8+
9+
10+
@code {
11+
[Parameter]
12+
public WeatherForecast[] Data { get; set; }
13+
14+
[Parameter]
15+
public List<string> Columns { get; set; }
16+
17+
[Parameter]
18+
public Func<int, Task> OnClick { get; set; }
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
@using WeatherForecast = Pages.GridRendering.WeatherForecast
2+
3+
<table class="table">
4+
<thead>
5+
<tr>
6+
@foreach (var item in Columns)
7+
{
8+
<th>@item</th>
9+
}
10+
</tr>
11+
</thead>
12+
<tbody>
13+
<CascadingValue Value="@this">
14+
<RowCollection Data="@Data"
15+
Columns="@Columns"
16+
OnClick="@RefreshComponent"></RowCollection>
17+
</CascadingValue>
18+
</tbody>
19+
</table>
20+
21+
22+
@code {
23+
[Parameter]
24+
public WeatherForecast[] Data { get; set; }
25+
26+
[Parameter]
27+
public List<string> Columns { get; set; }
28+
29+
DateTime t1;
30+
DateTime t2;
31+
Task RefreshComponent(int index)
32+
{
33+
t1 = DateTime.Now;
34+
StateHasChanged();
35+
return Task.CompletedTask;
36+
}
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+
}
46+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@typeparam TRowData
2+
3+
<CascadingValue IsFixed="true" Value="this">@ChildContent</CascadingValue>
4+
5+
<table @attributes="@Attributes">
6+
<thead>
7+
<tr>
8+
@foreach (var col in columns)
9+
{
10+
col.RenderHeader(__builder);
11+
}
12+
</tr>
13+
</thead>
14+
<tbody>
15+
@foreach (var item in Data)
16+
{
17+
<tr @key="item.GetHashCode()" class="@(RowClass?.Invoke(item))">
18+
@foreach (var col in columns)
19+
{
20+
col.RenderCell(__builder, item);
21+
}
22+
</tr>
23+
}
24+
</tbody>
25+
</table>
26+
27+
@code {
28+
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> Attributes { get; set; }
29+
[Parameter] public ICollection<TRowData> Data { get; set; }
30+
[Parameter] public RenderFragment ChildContent { get; set; }
31+
[Parameter] public Func<TRowData, string> RowClass { get; set; }
32+
33+
private List<GridColumn<TRowData>> columns = new List<GridColumn<TRowData>>();
34+
35+
internal void AddColumn(GridColumn<TRowData> column)
36+
{
37+
columns.Add(column);
38+
}
39+
40+
protected override void OnAfterRender(bool firstRender)
41+
{
42+
if (firstRender)
43+
{
44+
// On the first render, we collect the list of columns, then we have to render them.
45+
StateHasChanged();
46+
}
47+
}
48+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@typeparam TRowData
2+
@using Microsoft.AspNetCore.Components.Rendering
3+
@code {
4+
[CascadingParameter] public Grid<TRowData> OwnerGrid { get; set; }
5+
[Parameter] public string Title { get; set; }
6+
[Parameter] public TRowData RowData { get; set; }
7+
[Parameter] public RenderFragment<TRowData> ChildContent { get; set; }
8+
9+
protected override void OnInitialized()
10+
{
11+
OwnerGrid.AddColumn(this);
12+
}
13+
14+
internal void RenderHeader(RenderTreeBuilder __builder)
15+
{
16+
<th>@Title</th>
17+
}
18+
19+
internal void RenderCell(RenderTreeBuilder __builder, TRowData rowData)
20+
{
21+
<td>@ChildContent(rowData)</td>
22+
}
23+
}

src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
<a href="renderlist">RenderList</a> |
77
<a href="json">JSON</a> |
88
<a href="orgchart">OrgChart</a> |
9-
<a href="timer">Timer</a>
9+
<a href="timer">Timer</a> |
10+
<a href="gridrendering">Grid</a>
1011

1112
<hr />
1213

0 commit comments

Comments
 (0)