🎄

ENCA 6日目: イテレーターの next メソッドをキャッシュする

2024/12/06に公開

イテレーターの next メソッドをキャッシュする

JavaScript エンジンに ES2015 の機能が入り始め、Web ディベロッパーたちがその便利さに感動していた頃の話。配列で for...of やスプレッド構文を使うのは確かに便利な一方で、単純な for 文の方が高速に実行できることが問題視されました。

原因は明白で for 文の場合は値を取り出すのにプロパティアクセスするだけですみますが、for...of やスプレッド構文の場合は値を取り出すのに毎回イテレーターの next メソッドを呼び出す必要があります。そこでパフォーマンスを改善するため、互換性の問題が起きない範囲で破壊的変更を入れる案が2つ出ました。

  1. %ArrayIteratorPrototype%.next といったビルトインイテレーターの next メソッドを変更不可能にし、それらから値を取り出す際にエンジンの最適化によって実際に next メソッドが呼ばれなかったとしても挙動が変わらないようにする
  2. 値を取り出す前にイテレーターの next メソッドをキャッシュし、値を取り出すときはそれを繰り返し呼び出す

1 については将来仕様が変わった場合に polyfill によって変更することが不可能になってしまうなどの問題があります。一方で 2 については函数呼び出しが相変わらず必要なものの、ビルトインイテレーターに限らず幅広くパフォーマンス改善出来ます。

結果的に 2 のみを入れることとなりました。

https://github.com/tc39/ecma262/issues/976

エンジンによる最適化

さて %ArrayIteratorPrototype%.next などのビルトインイテレーターが変更可能な状態のままとなりました。しかし通常それらが書き換えられることはありません。

書き換えられていないことをチェックし next メソッドを呼び出さなくても仕様違反が起きないとわかった場合にファストパスを通って最適化することが可能です。実際に V8 でそのような実装がなされていることが記事に書かれています。

https://v8.dev/blog/spread-elements#tread-carefully-down-that-fast-path

他のコレクションについて

SetMap といった他のコレクションのコンストラクタでレシーバー(this)の addset のようなメソッドを呼び出しています。イテレーター同様これらもキャッシュして呼び出すことで最適化できそうです。この件についての issue はあるのですが、どうやら放置されてしまっているようです。

https://github.com/tc39/ecma262/issues/1430

Discussion