👌

emscripten(WASM) 向け省メモリプログラミングのメモ

に公開

背景

wasm では 2GB が基本限界であるので, 画像処理とかで結構限界に達しやすい.
memory64 にする手もあるが, Safari が 2025/10 時点未対応. また memory64 でも 16GB がハードリミットとなる.

WASM では, メモリ(heap)は原始的(?)なリニアアドレスで管理となる.
したがって std::vector とかで contiguous なメモリを要求する場合だと heap の容量が増えやすい
たとえば std::vector の copy だけでも二倍(これは通常の C++ でもそうであるが)のメモリを消費する.

その他, 100MB と 105 MB のメモリ確保があり, 100MB は解放してから 105 MB 確保する場合でも, 105 MB のメモリ確保するには空きがないため, 総計では 205 MB を必要とする.

native 環境の malloc であれば OS のページングなどで実メモリは減らせるかもしれないが, wasm の場合はそのような機能はないのでめんどい

また, JS/WASM をまたぐ場合, runtime の構造上, JS -> WASM ではメモリ共有は不可であるのでなにも考えないと二倍のメモリ容量が必要となる.
WASM -> JS では WASM 側の heap を view として見せることは可能(WebGL などの場合はこれでゼロコピー(GPU メモリ側へのコピーは必要となるが)は可能となる)

メモリ確保の report

とりあえずは JS/WASM は nodejs で動かせるようにしてメモリ消費の確認ができるとよいでしょう.
(nodejs 24 くらいから, memory64 対応な wasm を動かすことができるようになりました)

function reportMemUsage() {
 const used = process.memoryUsage()
 const messages = []
 for (let key in used) {
    console.log(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`)
  }
}

こんな感じでレポート可能.

TODO

詳細なメモリ確保の report できるようにする.

std::vector

コピーの場合2倍のメモリ消費となる.

ちなみに
std::vector<float>std::vector<int> に変えたい場合に, バッファを共有して reinterpret_cast 使う場合は未定義動作となる(はず). まあ概ね gcc, clang, msvc では問題なく動くとは思うが, debug info などある場合はうまくいかないかもしれません.

vector への emplace_back

class BigMem : なんか member として std::vector<> でメモリを結構使うクラス

std::vector<BigMem> a;
BigMem b;

a.emplace_back(std::move(b);

一見すると BigMem 一個ぶんのメモリでいけそうであるが, a の resize で? BigMem の copy ctor が呼ばれるのでメモリ消費は最大二倍.

a.resize(a.size() + 1);
a.back() = std::move(b);

とすることで, カラに近い BigMem を最初に確保してから, あとで move できるのでメモリ効率がよい.

chunked vector

std::vector はメモリ連続(contiguous)を保証しなければならない.

emscripten(WASM)の場合, chunked vector みたいな感じで chunk(block) 単位でメモリ確保し, 連続しないメモリ確保で対応できるのが推奨.
そのため独自に ChunkedVector みたいなクラスを作るとよいでしょう.

詳細は ChatGPT クンなどに聞いて.
C++20 などであれば ranges でいい感じに抽象化できるカモ.

JS: stream fetch

fetch は chunk (offset) 指定で stream read することは可能.
これにより chunk ごとに WASM 側にデータコピーして, 省メモリ化は可能

JS -> WASM で byob?(Bring Your Own Buffer)

↑を効率化するものとして, ReadableStreamBYOBReader で, WASM 側の heap をバッファにして fetch するなどできる.
ただし 2025/09 時点では Safari は未対応

https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader

Discussion