Blazor WebAssemblyのAOTコンパイルで処理が遅くなることもある

# はじめに

.NET 6からサポートされたBlazor WebAssemblyのAOT (Ahead-of-time) コンパイルについて、Qiitaのアドベントカレンダーの[祝 .NET 6 GA！.NET 6 での開発 Tips や試してみたことなど、あなたの「いち推し」ポイントを教えてください【PR】日本マイクロソフト](https://qiita.com/advent-calendar/2021/microsoft)に以下の記事を投稿しました。

https://qiita.com/kaorumori/items/88d836fab1fcbbbb7114

*Conway's Game of Life* を題材にしてAOTコンパイルやAzure Static Web Appsへのデプロイの手順についてまとめましたので、ぜひご覧くださればと思います。

上記の記事ではAOTコンパイルによって高速化できたことを示しましたが、この記事では画像処理をAOTコンパイルして処理が遅くなったケースを紹介したいと思います。

# Blazor WebAssemblyで画像処理

まず最初にBlazor WebAssemblyで画像処理をする簡単なサンプルアプリを作成します。

## サンプルアプリの作成

以下のコマンドを実行してAOTコンパイルのために必要なワークロードをインストールします。

```
> dotnet workload install wasm-tools
```

Visual Studio 2022でBlazor WebAssemblyのプロジェクトを作成して、NuGetで`SixLabors.ImageSharp`をインストールします。

画像処理のサンプルとして指定した画像をImageSharpでエッジ検出した結果を表示します。`Pages/Index.razor`を以下のように編集します。

```csharp:Pages/Index.razor
@page "/"

@using SixLabors.ImageSharp;
@using SixLabors.ImageSharp.PixelFormats;
@using SixLabors.ImageSharp.Processing;

<PageTitle>Index</PageTitle>

<p>
    <InputFile OnChange="@LoadImages" accept=".png,.jpg,.jpeg,.bmp,.gif" />
</p>

@if (@isLoading)
{
    <p>Loading...</p>
}
else if (@processedImage != null)
{
    MemoryStream stream = new();
    processedImage.SaveAsPng(stream);
    <p>
        <img src="data:image/png;base64,@Convert.ToBase64String(stream.ToArray())" style="width: 600px;" />
    </p>
    <p>Elapsed Time: @(stopwatch.ElapsedMilliseconds / 1000.0) seconds</p>
}


@code {
    private long maxFileSize = 1024 * 1024 * 24;
    private int maxAllowedFiles = 1;
    private bool isLoading = false;
    private System.Diagnostics.Stopwatch? stopwatch;
    private Image<Rgba32>? processedImage;

    private async Task LoadImages(InputFileChangeEventArgs e)
    {
        isLoading = true;
        StateHasChanged();
        stopwatch = new();
        stopwatch.Start();

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            MemoryStream stream = new();
            await file.OpenReadStream(maxFileSize).CopyToAsync(stream);
            var image = Image.Load(stream.ToArray());
            processedImage = image.Clone(img => img.DetectEdges());
        }

        stopwatch.Stop();
        isLoading = false;
    }
}
```

## 動作確認

### 起動

Visual Studio 2022のツールバーの`デバッグ > デバッグ`なしで開始を選択するとアプリが起動して以下のような画面が表示されます。

![起動画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/bc02db2e-9c71-9a7a-9bc3-4538bd27cded.png)

### 画像の指定と実行

起動した画面で`ファイルの選択`ボタンをクリックして画像を指定します。画像を指定すると`Loading...`と表示され、しばらくするとエッジ検出された結果の画像が表示されます。結果の画像の下に処理時間が表示されます。

![画像処理結果の表示画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2238166/ff116dda-f170-7b1d-b524-67375799b71f.png)

# 処理時間の計測

デフォルトの設定で公開ファイルを作成したものと、AOTコンパイルを有効にして公開ファイルを作成したもので処理時間を作成します。これらの具体的な手順については、[冒頭で紹介した記事](https://qiita.com/kaorumori/items/88d836fab1fcbbbb7114)を参照ください。

画像サイズが512x512と1240x1753の画像を処理した結果は以下のようになり、AOTコンパイルの方が遅くなってしまいました。

| 画像サイズ | AOTコンパイルあり | AOTコンパイルなし |
|---|--:|--:|
| 512x512 | 2.432 秒 | 2.029 秒 |
| 1240x1753 | 20.129 秒 | 17.603 秒 |

画像処理をエッジ検出ではなくてぼかし効果にするとか、いろいろな画像処理を組み合わせたり回数を重ねたりと試行しましたが、AOTコンパイルの方が高速化するようなケースの特定には至りませんでした。

# おわりに

Blazor WebAssemblyでImageSharpを使ってAOTコンパイルを有効にして画像処理をしたら処理が遅くなったケースを紹介しました。今回はImageSharpで提供されているメソッドを利用して画像処理をしました。一方で私が開発しているマークシート処理システムのMark2でもImageSharpを利用していて、Mark2の画像処理ではAOTコンパイルによって約6倍の高速化が実現できているため、画像処理の種類によってはAOTコンパイルが有効な場合もありそうです。よくわからないままとなっていて心残りですが、こういった情報も何かの役に立てばと思ってまとめました。