Blazor Server は遅いのか?
先日、こんな記事を書きました。
"WebSocket で通信できない環境では Blazor Server は動作しないのか?"
記事内容としては、Blazor Server アプリケーションは WebSocket で通信できない環境であっても Long Polling にフォールバックしてちゃんと稼働しますよ、ただし、処理性能的にはやっぱり WebSocket のほうが望ましいよ、という内容でした。で、WebSocket で通信できている場合と Long Polling にフォールバックして通信している場合とで、どれくらい処理性能に差がでるのか? を、Blazor Server 上で "ライフゲーム" を実装・実行して、それで処理速度を比較したのでした。
ちなみに "ライフゲーム" とは何か? については、ネットで検索して見つけた、下記記事などが面白くわかりやすいかと思います。
さて、前記の記事中で実装した 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 ライセンスで公開されているものを使います。
このライフゲームエンジンは、Cells
という int
型の 2 次元配列のプロパティを公開しています。この Cells
プロパティに、個体の生存世代数が格納されているので (0 ならばそのセルに個体が生存していないことを意味します)、これを HTML ページ上に描画・表示すればよいです。
前記の記事では、Cells
プロパティの内容を HTML Canvas に描画していたのですが、前述のとおり今回は簡潔に div 要素を並べて表示することにします。実装は下記のとおりです。for
文による二重ループで、単純に Cells
プロパティの2次元配列を div 要素でレンダリングだけですね。セルの世代数は data-age
という属性で書き出すようにしました (6世代目以降は6とだけ書くようにしました)。
<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 で指定します。
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()
を呼び出して次世代を描画させる、を無限に繰り返すようにします。
...
@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 倍ほど速いですね!
Azure App Service - 東日本リージョン
ローカル開発環境ではブラウザ-サーバー間の通信が localhost
なので、まぁ、ブラウザ-サーバー間の通信レイテンシは実質ゼロでしょうから、Blazor Server が高速に動作するのは当然ではあります。では、この Blazor Server 版ライフゲームをインターネット上のサーバーに配置して、インターネット経由での通信となった場合にどうなるか見てみましょう。
クラウドサービス Microsoft Azure の Azure App Service 上に、この Blazor Server 版ライフゲームを配置して試してみることにします。まずは、東日本リージョンに、無償プラン F1 に配置してみました。通信がインターネット越しになるので、ローカル開発環境で実行したときより、1フレーム描画にかかる時間が長くなるのでは・処理時間は遅くなるのではないかと予想されましが、結果は意外にも、概ね 4~5 msec と、ローカル開発環境と遜色ない処理速度になりました!
この結果を見るに、この 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倍ほど遅い結果となりました。
やはり Blazor Server の動作原理上の弱みのひとつとして、通信経路の質に処理性能が左右されるようです。それでも日本国内から米国西リージョンに配置したサーバーへのアクセスで約 15 msec/フレーム、fps (フレーム/秒) でいうと約 67 fps くらいでている計算になりますから、そう捨てたものではないですね。
まとめ
かなり雑な実験ですので、あまり正確に数値化できていませんが、とにかく、セルを div 要素で描画するだけの、何の技巧も凝らすことなく実直に実装した 60 x 60 セルの Blazor Server 版ライフゲームでは、1フレームあたり概ね 5 msec、200 fps くらいの描画速度で動作するらしいことがわかりました。
もちろん、Blazor Server はその動作原理上、どうしても通信の速度・レイテンシによって性能がひっぱられます。それでもブラウザとサーバーとがいずれも国内にあるケースでは、この実装ではローカル開発環境での動作と同程度の処理性能で実行できました。
処理速度には様々な要因が関係しますので、なかなかに要因の推定や解釈は難しいものがあります。それでもなお、特別な最適化実装は抜きに、愚直にライフゲームを実装しただけでも、ざっくり、これくらいの処理速度が出せたという実績は、何かしらの指標になるのではないかと思われました。
Learn, Practice, Share!