ENCA 6日目: イテレーターの next メソッドをキャッシュする
イテレーターの next メソッドをキャッシュする
JavaScript エンジンに ES2015 の機能が入り始め、Web ディベロッパーたちがその便利さに感動していた頃の話。配列で for...of やスプレッド構文を使うのは確かに便利な一方で、単純な for 文の方が高速に実行できることが問題視されました。
原因は明白で for 文の場合は値を取り出すのにプロパティアクセスするだけですみますが、for...of やスプレッド構文の場合は値を取り出すのに毎回イテレーターの next メソッドを呼び出す必要があります。そこでパフォーマンスを改善するため、互換性の問題が起きない範囲で破壊的変更を入れる案が2つ出ました。
-
%ArrayIteratorPrototype%.nextといったビルトインイテレーターのnextメソッドを変更不可能にし、それらから値を取り出す際にエンジンの最適化によって実際にnextメソッドが呼ばれなかったとしても挙動が変わらないようにする - 値を取り出す前にイテレーターの
nextメソッドをキャッシュし、値を取り出すときはそれを繰り返し呼び出す
1 については将来仕様が変わった場合に polyfill によって変更することが不可能になってしまうなどの問題があります。一方で 2 については函数呼び出しが相変わらず必要なものの、ビルトインイテレーターに限らず幅広くパフォーマンス改善出来ます。
結果的に 2 のみを入れることとなりました。
エンジンによる最適化
さて %ArrayIteratorPrototype%.next などのビルトインイテレーターが変更可能な状態のままとなりました。しかし通常それらが書き換えられることはありません。
書き換えられていないことをチェックし next メソッドを呼び出さなくても仕様違反が起きないとわかった場合にファストパスを通って最適化することが可能です。実際に V8 でそのような実装がなされていることが記事に書かれています。
他のコレクションについて
Set や Map といった他のコレクションのコンストラクタでレシーバー(this)の add や set のようなメソッドを呼び出しています。イテレーター同様これらもキャッシュして呼び出すことで最適化できそうです。この件についての issue はあるのですが、どうやら放置されてしまっているようです。
Discussion