導入
前回記事( https://qiita.com/Kujiro/items/f6d09167031320ae17bb )に引き続き、Blazor WebAssemblyのパフォーマンス改善の話です。
.NET6でC# => JS でのバイト配列の取り扱いが変わりました。破壊的変更ということでMicrosoftの方でも紹介されています。( https://docs.microsoft.com/ja-jp/dotnet/core/compatibility/aspnet-core/6.0/byte-array-interop )
実際何が変わったか試してみました。
何が変わったか
まずこんな感じのJavascriptを書きます。ファイルはwwwroot直下に配置するものとします。
export function ByteArrayTest(arg) {
console.log(arg);
}
次にC#から呼び出します。
// @inject IJSRuntime JSRuntime; <= これが書いてある前提
private async Task Hoge()
{
var module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./TestScript.js");
var array = Enumerable.Range(1, 1 << 20).Select(x => (byte)x).ToArray();
await module.InvokeVoidAsync("ByteArrayTest", array);
}
.NET5で実行するとBase64エンコードされた文字列が表示されます。
AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFy
(以下略、1.4MB)
.NET6で実行するとUint8Arrayが表示されます。
Uint8Array(1048576)
確かに破壊的変更です。破壊的変更に結構慎重なC#/.NETで破壊的変更になるということは Base64エンコードのオーバヘッドが消えた分のパフォーマンス改善は結構大きいのでしょうか?
ちなみに、C#側のbyte[]とJS側のUInt8Arrayは流石に共有はされていません。それぞれ独立に書き換えられます。
パフォーマンス測定
実際どれくらい差が出るのか試してみました。
PC環境は前回の記事と同様、.NET5/.NET6ともAOTはなしでReleaseビルドにします。
JS側の関数は空にします(単に呼び出しのオーバヘッドだけを測ります)
export function ByteArrayTest(arg) {}
C#側のコードは10回実行して平均と最大をとります。
引数のサイズは1MBになるようにしています。
// @inject IJSRuntime JSRuntime
private async Task DoCallTest()
{
var module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./TestScript.js");
var array = Enumerable.Range(1, 1 << 20).Select(x => (byte)x).ToArray();
List<TimeSpan> list = new();
for (int i = 0; i < 10; i++)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
await module.InvokeVoidAsync("ByteArrayTest", array);
stopwatch.Stop();
list.Add(stopwatch.Elapsed);
}
Result = ($"arg-size={array.Length},ave:{list.Select(x => x.TotalMilliseconds).Average()}ms,max={list.Max().TotalMilliseconds}ms");
}
結果
環境 | 平均 | 最大 |
---|---|---|
.NET5 | 450ms | 471ms |
.NET6 | 2.02ms | 10.9ms |
220倍早いです。全然違いますね。
感想
Span<T>
も渡せたらいいのに...
JS呼び出しの目的ってJS相互運用のみならず単にIndexedDB使いたいとかWeb Workers API使いたいとかっていうケースも多いので
stringとかもそのままUnit8Arrayで渡せると高速化できそうですね。