普通に Blazor Server でライフゲームを実装したら、どれくらいの処理速度になるのか?

## Blazor Server は遅いのか?

先日、こんな記事を書きました。

_"WebSocket で通信できない環境では Blazor Server は動作しないのか?"_

https://qiita.com/jsakamoto/items/4f7bfbbb3e3ba2f31614

記事内容としては、Blazor Server アプリケーションは WebSocket で通信できない環境であっても Long Polling にフォールバックしてちゃんと稼働しますよ、ただし、処理性能的にはやっぱり WebSocket のほうが望ましいよ、という内容でした。で、WebSocket で通信できている場合と Long Polling にフォールバックして通信している場合とで、どれくらい処理性能に差がでるのか? を、Blazor Server 上で "ライフゲーム" を実装・実行して、それで処理速度を比較したのでした。

ちなみに "ライフゲーム" とは何か? については、ネットで検索して見つけた、下記記事などが面白くわかりやすいかと思います。

https://pineapple.blog/%E3%82%B3%E3%83%B3%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0-%E5%89%8D%E7%B7%A8-39f42f48741

さて、前記の記事中で実装した Blazor Server 版ライフゲームは、60 x 60 セルを 1 フレーム描画するのに平均 500 msec ほどかかっていました。これを見て
**「えー、Blazor Server って、たかが 60 x 60 セルのライフゲームを描画するのに、そんなに遅いのー!?」**
とびっくりされたかもしれませんが、もちろん、**そんなことはありません!**

前記の記事中で実装した Blazor Server 版ライフゲームは、速度比較のためにあえて処理速度が遅くなるよう、**わざと Blazor Server の苦手なやり方**、すなわち、大量の JavaScript 呼び出しを介して HTML の Canvas 要素に描画する、そういう実装をしていたので、それで1フレーム描画に随分時間がかかっている次第です。

## "普通に" ライフゲームを実装しなおしてみよう

それじゃぁですね、もうちょっと素直にライフゲームを Blazor Server で実装したら、どれくらいの処理速度になるのでしょうか?

ということで、**処理速度を稼ぐ技巧的な実装などは一際せず**に、ただし HTML Canvas に描画するのではなくて、div 要素でセルを表現、CSS で外観調整する、そういう**質素な普通の実装**で、実際に Blazor Server 版ライフゲームを実装し直してみました。

まず、ライフゲームのエンジン部分は、前記の記事中で使用したのと同じく、下記リンク先の GitHub リポジトリで MIT ライセンスで公開されているものを使います。

https://github.com/Timmoth/GameOfLife

このライフゲームエンジンは、`Cells` という `int` 型の 2 次元配列のプロパティを公開しています。この `Cells` プロパティに、個体の生存世代数が格納されているので (0 ならばそのセルに個体が生存していないことを意味します)、これを HTML ページ上に描画・表示すればよいです。

前記の記事では、`Cells` プロパティの内容を HTML Canvas に描画していたのですが、前述のとおり今回は簡潔に div 要素を並べて表示することにします。実装は下記のとおりです。`for` 文による二重ループで、単純に `Cells` プロパティの2次元配列を div 要素でレンダリングだけですね。セルの世代数は `data-age` という属性で書き出すようにしました (6世代目以降は6とだけ書くようにしました)。

```csharp:*.razor
<div class="container">
    @for (var i = 0; i < gameOfLife.Height; i++)
    {
        <div @key="i" class="row">
            @for (var j = 0; j < gameOfLife.Width; j++)
            {
                var age = gameOfLife.Cells[i, j];
                <div @key="j" data-age="@(age > 5 ? 6 : age)"></div>
            }
        </div>
    }
</div>
```
世代ごとにセルの色を赤 > 黄 > 青 に塗り分けるよう、`data-age` 属性値に応じた背景色を CSS で指定します。
```css:*.css
div[data-age="1"] {
    background-color: #d41515;
}
div[data-age="2"] {
    background-color: #d46515;
}
div[data-age="3"] {
    background-color: #d4c415;
}
div[data-age="4"] {
    background-color: #68d415;
}
div[data-age="5"] {
    background-color: #15d4a4;
}
div[data-age="6"] {
    background-color: #1558d4;
}
```

あとは描画サイクルを実装します。すなわち、Razor コンポーネントで 1フレーム描画終わったら `OnAfterRender` ライフサイクルメソッドが呼び出されますから、その `OnAfterRender` メソッド内で、ライフゲームエンジンを1世代進ませて、すかさず `StateHasChanged()` を呼び出して次世代を描画させる、を無限に繰り返すようにします。

```csharp:*.razor
...
@code {
  ...
  protected override void OnAfterRender(bool firstRender)
  {
    gameOfLife.Next();
    StateHasChanged();
  }
}
```

以上でできあがりです! JavaScript 相互運用機能も使わず、**Blazor プログラミングの基本的な機能要素しか使っていない**、すごくオーソドックスな実装になったのではないかと思います。

## それで、処理速度はどれくらい?

### ローカル開発環境

以上の実装を、ローカル開発環境 (11th Gen Intel Core i7-1185G7, Microsoft Edge v.108) で `dotnet run` で実行したところ、**1フレーム概ね 4～5 msec くらい**の描画速度になりました! 前記記事での実装と比べ **100 倍ほど速い**ですね!
![movie-001.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/5753c60c-7efb-a641-3a29-ade21784da73.gif)

### Azure App Service - 東日本リージョン

ローカル開発環境ではブラウザ-サーバー間の通信が `localhost` なので、まぁ、ブラウザ-サーバー間の通信レイテンシは実質ゼロでしょうから、Blazor Server が高速に動作するのは当然ではあります。では、この Blazor Server 版ライフゲームをインターネット上のサーバーに配置して、インターネット経由での通信となった場合にどうなるか見てみましょう。

クラウドサービス Microsoft Azure の Azure App Service 上に、この Blazor Server 版ライフゲームを配置して試してみることにします。まずは、東日本リージョンに、無償プラン F1 に配置してみました。通信がインターネット越しになるので、ローカル開発環境で実行したときより、1フレーム描画にかかる時間が長くなるのでは・処理時間は遅くなるのではないかと予想されましが、**結果は意外にも、概ね 4～5 msec と、ローカル開発環境と遜色ない処理速度**になりました!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/9c737734-cb5b-892f-1809-263b4f2764a4.png)

この結果を見るに、この Blazor Server 版ライフゲームの処理性能上のボトルネックは、通信部分にはない可能性がありそうですね。インターネット越しでも国内のサーバーであれば 1 フレーム描画するのに約 5 msec ほど、fps (フレーム/秒) に換算すると **200 fps** くらいはいけるようです。1フレーム描画する度に 60 x 60 セル = 最大で **3,600 個の div 要素を描画更新**していることを考えると、一般的な Web アプリケーションとしてはじゅうぶんな処理性能と言えるのではないでしょうか。

### Azure App Service - 米国西リージョン

もうひとつ試しに、同じ Azure App Service F1 プランの、今度は**米国西**リージョンに配置して試してみました。すると、さすがに遠い米国上のサーバーからだと通信速度、レイテンシの影響でしょうか、**概ね 14～15 msec** と、ローカル開発環境や Azure App Service F1 東日本リージョンと比べて、**ざっくり3倍ほど遅い**結果となりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58450/979f1099-9cf5-bb96-aadc-96f256a52c95.png)

やはり Blazor Server の動作原理上の弱みのひとつとして、通信経路の質に処理性能が左右されるようです。それでも日本国内から米国西リージョンに配置したサーバーへのアクセスで約 15 msec/フレーム、fps (フレーム/秒) でいうと約 67 fps くらいでている計算になりますから、そう捨てたものではないですね。

## まとめ

かなり雑な実験ですので、あまり正確に数値化できていませんが、とにかく、セルを div 要素で描画するだけの、**何の技巧も凝らすことなく実直に実装**した 60 x 60 セルの Blazor Server 版ライフゲームでは、**1フレームあたり概ね 5 msec、200 fps くらいの描画速度**で動作するらしいことがわかりました。

もちろん、Blazor Server はその動作原理上、どうしても通信の速度・レイテンシによって性能がひっぱられます。それでもブラウザとサーバーとがいずれも国内にあるケースでは、この実装ではローカル開発環境での動作と同程度の処理性能で実行できました。

処理速度には様々な要因が関係しますので、なかなかに要因の推定や解釈は難しいものがあります。それでもなお、特別な最適化実装は抜きに、愚直にライフゲームを実装しただけでも、ざっくり、これくらいの処理速度が出せたという実績は、何かしらの指標になるのではないかと思われました。

_Learn, Practice, Share!_