3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

BlazorAdvent Calendar 2021

Day 15

Blazor WebAssemblyで2GBの壁を超え4GBへ

Posted at

この記事は 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まで使用できるのか確かめてみます。

デフォルトの状態での検証

スクリーンショット 2021-12-14 22.38.07.png

順に 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 の順番で入力してみると…

スクリーンショット 2021-12-14 23.09.32.png

何ということでしょう、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 を入力しています。

スクリーンショット 2021-12-14 23.18.27.png

4だと桁あふれしてしまっていますね…
ということで3.99ぐらいを試してみましたがこの辺りは駄目なようですね。

では刻んでみましょう。

まずは3ぐらいを確保してみましょう。

スクリーンショット 2021-12-14 23.22.00.png

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だけど、実際本当に使えるか神のみぞ知るというオチになってしました。

3
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?