📏

TypedArray のサイズが可変になる ES2024 Resizable ArrayBuffers

2023/01/08に公開
変更情報

【2024/06/28】

  • ES2024 として仕様に入ったためタイトル、本文を更新

今までの ArrayBuffer の問題

今まで JavaScript の ArrayBuffer はサイズが変わることを想定していませんでした。そのためサイズを変える必要がある API の仕様を作るとなると ECMAScript サイドではなく、その API サイドでの対応が必要な状況になっていました。

例えば WebAssembly.Memory を見てみましょう。

https://webassembly.github.io/spec/js-api/#memories

[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
interface Memory {
  constructor(MemoryDescriptor descriptor);
  unsigned long grow([EnforceRange] unsigned long delta);
  readonly attribute ArrayBuffer buffer;
};

バッファのサイズを大きくしたいとき grow メソッドを呼び出します。しかし ArrayBuffer はサイズが変わることを想定していなかったため、buffer プロパティで取得していた ArrayBuffer が detached されていました。再度 buffer プロパティにアクセスして新しい ArrayBuffer を取得しなければなりませんでした。

いつ detached されるかわからない場合、以下のようにバッファにアクセスするたびに確認する必要があります。これでは扱いにくい上に処理速度がかなり遅くなります。

let uint8 = new Uint8Array(memory.buffer);

function getUint8MemoryValue(index) {
  // memory.buffer が detached されていたら、再度 Uint8Array を作り直す
  if (uint8.length === 0) {
    uint8 = new Uint8Array(memory.buffer);
  }
  
  return uint8[index];
}

ES2024 Resizable ArrayBuffers

ArrayBuffer のサイズを可変にするのが ES2024 Resizable ArrayBuffers です。

https://github.com/tc39/proposal-resizablearraybuffer

扱い方

ArrayBuffer コンストラクタの第二引数にオプションが追加され、maxByteLength が指定できるようになります。それ以下の数値なら resize メソッドでサイズを変更できます。

const buffer = new ArrayBuffer(100, { maxByteLength: 200 });
console.log(buffer.length); // 100
console.log(buffer.maxByteLength); // 200
console.log(buffer.resizable); // true

buffer.resize(150);
console.log(buffer.length); // 150

これが ECMAScript に入ることによって WebAssembly.Memorygrow メソッドが呼ばれても、 buffer プロパティが detached されず常に同じ ArrayBuffer オブジェクトを返すようになることが期待されます。

注意点

今まで ArrayBuffer は固定サイズを扱うものだったため、予めそのサイズをキャッシュしておいてそれを使い回すコードを書いている場合注意が必要そうです。

特に注意が必要な点として TypedArrayDataView をコンストラクタから作る際に第一引数にサイズが可変な ArrayBuffer を入力し、かつ第三引数(length)が undefined の場合、ArrayBuffer のサイズ変更に引きずられることが挙げられます。

const buffer = new ArrayBuffer(100, { maxByteLength: 200 });
const uint8 = new Uint8Array(buffer);
console.log(uint8.length); // 100

buffer.resize(150);
console.log(uint8.length); // 150

解決したい問題のことを考えるとこうならざるを得ませんが、今までの前提と異なる挙動となります。今一度既存のコードを確認して、その都度 length プロパティにアクセスする方針に修正したほうがいいかもしれません。

Growable SharedArrayBuffers

SharedArrayBuffer はサイズを小さくすることが想定されないため少し異なりますが、ほとんど Resizable ArrayBuffers と同じような機能追加がなされます。

関連する仕様

ES2024 ArrayBuffer transfer

C における realloc のように、メモリを再確保するメソッドを追加する仕様です。このメソッドが呼ばれたあとは元の ArrayBuffer は detached されます。

https://github.com/tc39/proposal-arraybuffer-transfer

結び

今回は Resizable ArrayBuffers について取り上げてみました。この仕様は人によっては対応が必要そうかなと思い記事にしてみました。

その他の提案の記事も Zenn に書いているのでよかったら読んでください。最後まで読んでいただきありがとうございました!

Discussion