この記事は Qiita Advent Calendar 2021 Blazor の15日目の記事です。
2日目の記事 でもお伝えしたとおり、Native Dependenciesがサポートされたので、メモリをもりもり使う様(?)なネイティブライブラリもブラウザで動作させられるようになりました。
流石にそんなデカイメモリを使うことはあるのか、というところではありますが、例えば音声合成や音声認識において、辞書ファイルや探索グラフをオンメモリに展開すると500MB~以上なんてことはザラにあったりします。
他にも動画ファイルを読み込んで処理したり、画像の加工をしたり…
そんなアプリケーションを動作させたいのであれば、Blazor WebAssemblyで広いメモリ空間を使えるようにする必要があります。
果たしてどこまでメモリを使うことが出来るのでしょうか?実験で試してみましょう。
忙しい人向けに3行まとめ
- Native依存を含めて
- MAXIMUM_MEMORYのビルドフラグを使えば
- 約4GBまで使える(はず
環境
- Intel mac mem 32 GB
- Google Chrome 96.0.4664.110
- .NET 6.0.100
検証コード
検証に使ったBlazor WebAssemblyのRazorファイルは以下の様な構成です。
@page "/"
@using System.Runtime.InteropServices
<label>
Allocate input GB
<input @bind-value="@InputValue" />
</label>
<button onclick="@DoAllocate">Allocate</button>
@code {
private string? InputValue { get; set; }
public void DoAllocate()
{
var gb = double.Parse(InputValue);
var totalAllocMem = (nuint)(1_073_741_824 * gb);
Console.WriteLine(totalAllocMem);
unsafe
{
var buffer = (byte*)NativeMemory.Alloc(totalAllocMem);
buffer[totalAllocMem - 1] = 1; // Can access!
NativeMemory.Free(buffer);
}
}
}
.NET 6から追加されたメモリ管理のAPIである NativeMemory.Alloc
をメモリの確保に使用しています。
byte*
型にキャストなどを行っていることからも、これはunsafe
な処理なので、csproj側にも追加しておきます。
PropertyGroup
の要素として
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
を追加し、unsafeコードをビルド出来る様にしておきましょう。
これでコードの準備が完了しました。
実際に何GBまで使用できるのか確かめてみます。
デフォルトの状態での検証
順に 1, 1.5, 1.9, 2
を入力しメモリの確保と解放を行っています。
画像から見て分かるように、2
を入力した段階でメモリ不足でクラッシュしてしまいました。
通常では2GBまでが制約のようです。
Emscriptenのビルドフラグを使用し限界突破を目指す
さて、本当に限界は2GBなのでしょうか?
32bitで表現できるアドレス空間は4GB程度あるはずと睨み、Emscriptenでどうにかならないか調べてみます。
そうすると見つかりました、Release Notesです!
https://emscripten.org/docs/introducing_emscripten/release_notes.html
この 2.0.13: 01/19/2021
のところを見ると
- Added support for -s MALLOC=emmalloc when -s MAXIMUM_MEMORY is more than 2GB.
.NET 6のwasm-toolsで使われているEmscriptenは2.0.17なので、このリリースは含まれています。
どうもこの MAXIMUM_MEMORY
フラグを使うことで2GBの壁を突破できそうです。
これを試してみましょう。
Native Dependenciesを利用した際にEmscriptenのタスクが走りましたが、その実態は Dotnet Workloadの wasm-tools
が正体です。
これ経由でビルドフラグを渡せないか探ってみると
<!--
// 略
Public properties (optional):
// 略
- $(EmccFlags) - Emcc flags used for both compiling native files, and linking
-->
とまさにこれといったものが見つかります。
このファイルは macOS だと /usr/local/share/dotnet/packs/Microsoft.NET.Runtime.WebAssembly.Sdk/6.0.0/Sdk/WasmApp.targets
に存在するのですが、GitHubのリポジトリで管理されているもので言うと
これが該当します。
プロパティもわかったところでcsprojの書き換えを行いましょう。
<PropertyGroup>
<EmccFlags>-s MAXIMUM_MEMORY=4GB</EmccFlags>
</PropertyGroup>
この状態でビルドし同様に確保するメモリを入力してみます。
1.5, 2
の順番で入力してみると…
何ということでしょう、4GBどころか先程の2GBも使い切れていません!
さて原因の究明です。
そういう時にはまずはビルドログということで、おもむろに
$ dotnet build -v d
を叩きビルドを始め、MAXIMUM_MEMORY
でgrepしてみましょう。
見つかりましたか?
そのとおり、このビルドフラグは使われていません。
そう、これは dotnet.wasm
をリビルドするタイミングでしか使われないのです。
どうにかして dotnet.wasm
をリビルドさせるにはどうしたら良いでしょうか?
そう、2日目の記事で紹介したNative Dependencies
を無理やり使って何かしらの外部ソースをリンクさせてしまえば良いのです。
ということでこんなCファイルをでっち上げます。
Test.c
とでも名付けておきましょう。
int main()
{
return 0;
}
見るからに何もしないコードですね。
これをcsprojに追加します。
<ItemGroup>
<NativeFileReference Include="Test.c" ScanForPInvokes="false" />
</ItemGroup>
何もしないコードなのでPInvokeの検索テーブルに追加する必要もありません、falseを設定しておきます。
この状態で再度ビルドログが流れるようにビルドすると無事 MAXIMUM_MEMORY
が使われていることが確認できると思います。
さてこれでメモリは使えるのでしょうか、試してみましょう。
順に1.5, 2, 3, 4, 3.99
を入力しています。
4だと桁あふれしてしまっていますね…
ということで3.99ぐらいを試してみましたがこの辺りは駄目なようですね。
では刻んでみましょう。
まずは3
ぐらいを確保してみましょう。
3GB!?確保できたはずでは!?
はい、ということで急に3GBは確保できないようです。
調査を進めてみた所、まず2GB確保、および開放し、その後3GBを確保であれば行うことが出来ました。
暖機運転が必要なようです。
この様な暖機運転後メモリを確保してみると 3.9GB
近くまでは確保ができました。
まとめ
Nativeな依存を含め MAXIMUM_MEMORY
のビルドフラグを追加することで約 4GB
までのメモリがBlazor WebAssemblyで使用できることがわかりました。
また段階的にメモリ確保の量を上げてGROWTHさせることが必要なことも併せて確認できました。
とは言えブラウザの1ページがこんなデカイメモリを確保したらどう考えてもユーザが困惑するので、必要なメモリを少しずつ確保するなどの対応は必要そうですね。
あとがき
とここまで書いたあとでIssueを探してみるとありました。
読んでみると
I guess it would be multiple things which would have to be fixed, not just this one.
オッ…実は何かしらあるかもしれませんね?
今回はAllocateして最後のメモリアドレスに対してアクセスが出来るかだけの検証でした。
実際にデカめのファイルを読ませたりなどすると実は動かない、とかは出てくるのかもしれません。
なにか見つけたらIssueにコメントなりして様子を見ることにします。
また4GB以上のメモリをビルドフラグに渡せるのか実験もしてみましたが、
wasm-ld : error : maximum memory too large, cannot be greater than 4294967296
あっ、はい。
ということで4GB Maxだけど、実際本当に使えるか神のみぞ知るというオチになってしました。