1.大量データを効率よく表示したい
- あまり望ましくないですが、大量データを表示するUIが必要なケースもあると思います
- 数100行程度なら良いですが、数千、数万行となったらメモリ効率を考えたいです
- Blazorの
<Virtualize>
タグを試したところ、劇的に改善 したので共有します
2.前提条件
2.1 環境
- Visual studio 2022 Version 17.12.2
- .Net 8
- UI:Blazor Web App
- Blazor rendermode:InteractiveServerRenderMode
2.2 テストデータ
- Weatherテーブル:50,000行 を全行表示
( 列は No, 日付, 気温℃, 概要 )
3.Virtualizeの有無による決定的な違い
3.1 Virtualizeなしの場合
対象データ量に比例してメモリ消費が増える。
3.2 Virtualizeありの場合
3GB -> 20MB へ大幅に改善
4.評価パターン
No | パターン | サーバーメモリ | ブラウザメモリ | 応答時間 |
---|---|---|---|---|
(1) | Virtualize なし | 460MB | 2997MB | 7178ms |
(2) | Virtualize 初期ロード | 69MB | 23MB | 1264ms |
(3) | Virtualize 必要ページロード | 3MB | 19MB | 11ms |
圧倒的にVirtualize(特に必要ページをロード)が効率的なことがわかる
4.1 Virtualizeの種類
種類 | Virtualize 初期ロード | Virtualize 必要ページロード |
---|---|---|
データ | すべてのデータを事前にロード | 必要な範囲のデータを動的に取得 |
指定方法 | Items 指定 | ItemsProvider 指定 |
メモリ | 全データがメモリに常駐 | 表示範囲 + バッファのデータのみメモリに保持 |
4.2 評価実行結果
(1)Virtualize なし
(2)Virtualize 初期ロード
(3)Virtualize 必要ページロード
5.ソース
(1)Virtualize なし
- OnInitializedAsyncで全データ取得
- 描画は普通のforeach
<tbody>
@foreach (var weather in weathers)
{
<tr>
<td>@weather.No</td>
<td>@weather.Date.ToShortDateString()</td>
<td>@weather.TemperatureC</td>
<td>@weather.Summary</td>
</tr>
}
</tbody>
@code {
private List<Weather> weathers = new List<Weather>();
protected override async Task OnInitializedAsync()
{
weathers = await WeatherService.GetAllWeathers();
}
}
呼び出しているデータ取得
public async Task<List<Weather>> GetAllWeathers()
{
var weathers = await _context.Weathers.ToListAsync();
return weathers;
}
(2)Virtualize 初期ロード
- OnInitializedAsyncで全データ取得
- 描画には
<Virtualize>
タグを利用 -
Items="@weathers"
にて全データを受け取る
<tbody>
<Virtualize Context="weather" Items="@weathers" OverscanCount="5" SpacerElement="tr">
<tr>
<td>@weather.No</td>
<td>@weather.Date.ToShortDateString()</td>
<td>@weather.TemperatureC</td>
<td>@weather.Summary</td>
</tr>
</Virtualize>
</tbody>
@code {
private List<Weather> weathers = new List<Weather>();
protected override async Task OnInitializedAsync()
{
weathers = await WeatherService.GetAllWeathers();
}
}
(3)Virtualize 必要ページロード
- 描画には
<Virtualize>
タグを利用 - 描画用にデータが必要になった時、必要なデータだけを取得
(必要な件数のみ取得) -
ItemsProvider="LoadWeathers"
にてデータが必要になった時に呼び出すイベントを指定
<tbody>
<Virtualize Context="weather" ItemsProvider="LoadWeathers" OverscanCount="5" SpacerElement="tr">
<tr>
<td>@weather.No</td>
<td>@weather.Date.ToShortDateString()</td>
<td>@weather.TemperatureC</td>
<td>@weather.Summary</td>
</tr>
</Virtualize>
</tbody>
@code {
private async ValueTask<ItemsProviderResult<Weather>> LoadWeathers(ItemsProviderRequest request)
{
var weathers = await WeatherService.GetRangeWeathers(request.StartIndex, request.Count);
return new ItemsProviderResult<Weather>(weathers, totalCount);
}
}
呼び出しているデータ取得
public async Task<List<Weather>> GetRangeWeathers(int startIndex, int Count)
{
var weathers = await _context.Weathers
.Skip(startIndex)
.Take(Count)
.OrderBy(x => x.No)
.ToListAsync()
;
return weathers;
}
6.おすすめの戦略
データ量が少なく、シンプルさを求める場合
→ (2)Virtualize 初期ロード を利用
データ量が多く、効率的に取得したい場合
→ (3)Virtualize 必要ページロード を利用
(2)Virtualize 初期ロード が適しているケース
- データ量が少ない場合(例: 数百件程度)
- データがすでにメモリ上に存在しており、頻繁に変更されない場合
- 初期ロードのパフォーマンスを優先したい場合
(3)Virtualize 必要ページロード が適しているケース
- データ量が多い場合(例: 数千〜数百万件)
- データを逐次取得することで、効率的にメモリとネットワークを活用したい場合
- リアルタイム更新や段階的なデータ取得が必要な場合
7.全ソース
8.参考