5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Blazor 大量データ表示を仮想化<Virtualize>して劇的に高速化にする

Last updated at Posted at 2024-12-03

1.大量データを効率よく表示したい

  • あまり望ましくないですが、大量データを表示するUIが必要なケースもあると思います
  • 数100行程度なら良いですが、数千、数万行となったらメモリ効率を考えたいです
  • Blazorの <Virtualize> タグを試したところ、劇的に改善 したので共有します

image.png

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なしの場合

  • 当然すべての行を取得している
    (50,000行のデータが、サーバー側とブラウザ上に展開される)
    image.png

  • ブラウザのタブで3GB近くメモリを消費してしまう
    image.png

対象データ量に比例してメモリ消費が増える。

3.2 Virtualizeありの場合

  • 表示範囲+α行しか取得しない
    image.png

  • ブラウザのタブで20MB未満しか消費しない
    image.png

3GB -> 20MB へ大幅に改善

  • スクロールすると取得しているエレメントの開始行がNo=1から変わる
    (ブラウザで表示できる範囲+αしか取得していない)
    image.png

  • スクロールすると、データを取得するSignalR上のバイナリデータ通信が確認できる
    image.png

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 なし

  • (82MB → 542MB) = 460MB
    image.png

(2)Virtualize 初期ロード

  • (82MB → 151MB) = 69MB
    image.png

(3)Virtualize 必要ページロード

  • (82MB → 85MB) = 3MB
    image.png

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.参考

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?