Object.prototype.toString()
実装および機能の機能について
Object.prototype.toString()
しています。 特に、この設計が重要である理由、ES2015キャラクターの出現によってどのように変化したか、V8で
toString()
パフォーマンスが約6倍向上したMozillaエンジニアが提案した最適化アプローチについて説明します。
![画像](https://habrastorage.org/getpro/habr/post_images/fdf/1d4/767/fdf1d4767f6ae00fe5d3b83ef8ed1c7f.jpg)
はじめに
ECMAScript 2015では、いわゆる既知のシンボルの概念が導入されました。 これらは、言語の内部メカニズムを表す特別な組み込み文字であり、ECMAScript 5および以前のバージョンの標準の実装では開発者がアクセスできません。
以下に例を示します。
- Symbol.iterator :これは、オブジェクトのデフォルトのイテレータを返すメソッドです。 これは、 for..of 、 yield * 、 拡張演算子 、 破壊的な割り当てなどの言語構造で使用されます。
- Symbol.hasInstance :コンストラクターオブジェクトがオブジェクトをそのインスタンスと見なすかどうかを決定するメソッド。 instanceof演算子によって使用されます 。
- Symbol.toStringTag :オブジェクトの説明に使用されるデフォルトの文字列値。 Object.prototype.toString()メソッドによってアクセスされます。
これらの新しい構成のほとんどは、言語のさまざまな部分で包括的かつ非自明に機能します。 これは、いわゆるモンキーパッチによるパフォーマンスプロファイルの大幅な変更につながります 。 プログラムの実行中にユーザーコードを使用していくつかの標準メカニズムを変更する可能性について話している。
特に興味深い例の1つは、新しいSymbol.toStringTagシンボルです。これは、組み込みのObject.prototype.toString()メソッドの動作を制御するために使用されます。 たとえば、開発者はオブジェクトの任意のインスタンスに特別なプロパティを配置できます。その後、
toString
メソッドを呼び出すときに、標準のインラインタグの代わりにこのプロパティが使用されます。
class A { get [Symbol.toStringTag]() { return 'A'; } } Object.prototype.toString.call(''); // "[object String]" Object.prototype.toString.call({}); // "[object Object]" Object.prototype.toString.call(new A); // "[object A]"
このため、ES2015以降のバージョンの標準の
Object.prototype.toString()
実装では、最初に抽象ToObject操作を使用してこの値をオブジェクトに変換し、次に、結果のオブジェクトとそのプロトタイプチェーンで
Symbol.toStringTag
を検索する
Symbol.toStringTag
があります。 言語仕様の対応する部分でそれについて見つけることができます:
![](https://habrastorage.org/getpro/habr/post_images/c3f/98e/ac2/c3f98eac2e8ab13181f9ee2996b6864b.png)
Object.prototype.toString()の仕様スニペット
ここでは、最初にToObjectを使用した変換、次に
@@toStringTag
Get呼び出しを見ることができます(これは、
toStringTag
という名前の有名な文字の言語仕様の特別な内部構文です)。
Symbol.toStringTag
コンストラクトをES2015に追加すると、開発者の機能が大幅に拡張されますが、同時に一定量のリソースも必要になります。
ToStringパフォーマンス研究の目標
ChromeおよびNode.jsの
Object.prototype.toString()
メソッドのパフォーマンスは、いくつかの一般的なフレームワークおよびライブラリによる型チェックに頻繁に使用されるため、 すでに調査されています。 そのため、AngularJSフレームワークはこのメソッドを使用して、 angle.isDate 、 angle.isArrayBuffer 、 angle.isRegExpなどのさまざまなヘルパー関数を実装します。 例:
/** * @ngdoc function * @name angular.isDate * @module ng * @kind function * * @description * Determines if a value is a date. * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Date`. */ function isDate(value) { return toString.call(value) === '[object Date]'; }
さらに、 lodashやunderscore.jsなどの一般的なライブラリは、
Object.prototype.toString()
を使用して値チェックを実装します。 そのため、たとえば、 lodashの述語_.isPlainObjectおよび_.isDateが配置されます。
/** * Checks if `value` is classified as a `Date` object. * * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a date object, else `false`. * @example * * isDate(new Date) * // => true * * isDate('Mon April 23 2012') * // => false */ function isDate(value) { return isObjectLike(value) && baseGetTag(value) == '[object Date]' }
SpiderMonkey JavaScriptエンジンに取り組んでいるMozillaエンジニアは、
Object.prototype.toString()
Symbol.toStringTag
検索
Symbol.toStringTag
が実際のアプリケーションのパフォーマンスのボトルネックであることを発見しました。 この結論は、ベンチマーク速度計の研究中になされました。 内部V8プロファイラーを使用して
--no-sandbox --js-flags=--prof
からAngularJSテストのみを実行します(有効にするには、コマンドラインキー
--no-sandbox --js-flags=--prof
起動時に
--no-sandbox --js-flags=--prof
を渡す必要があります)。
@@toStringTag
検索(
GetPropertyStub
)の実行と、組み込み
Object.prototype.toString()
メソッドを実装する
ObjectProtoToString
コードの実行に費やされます。
![](https://habrastorage.org/getpro/habr/post_images/042/792/75e/04279275e955ac0a9b0bda73a4c49bb5.png)
SpeedometerベンチマークからのAngularJSサブテストプロファイリング
SpiderMonkey開発チームのJan de Moischは、配列内の
Object.prototype.toString()
パフォーマンスをテストするための簡単なマイクロベンチマークを作成しました。
function f() { var res = ""; var a = [1, 2, 3]; var toString = Object.prototype.toString; var t = new Date; for (var i = 0; i < 5000000; i++) res = toString.call(a); print(new Date - t); return res; } f();
実際、V8に組み込まれている内部プロファイラーを使用したこのマイクロ
--prof
コマンドライン
--prof
を使用してd8シェルで有効にすることができます)は、すでに問題の本質を示しています。 主なリソースは、配列
[1, 2, 3]
Symbol.toStringTag
検索に
Symbol.toStringTag
れます。 合計実行時間の約73%は、結果を与えないプロパティ検索(ユニバーサルプロパティ検索を実装する
GetPropertyStub
関数)に費やされ、さらに3%は組み込み
ToObject
関数に
ToObject
ます。これは、配列の場合、空の操作(配列、 JavaScriptはすでにオブジェクトです)。
![](https://habrastorage.org/getpro/habr/post_images/ae7/460/9bf/ae74609bff0402ff1a054abf373c196b.png)
プロファイラーによるMozillaマイクロベンチマークの調査(最適化前)
面白いキャラクター
SpiderMonkeyについては、上記の問題の解決策が提案されました。これは、オブジェクトに興味深いシンボルを追加することで構成されています。 このシンボルは任意の非表示クラスのプロパティであり、この非表示クラスを持つオブジェクトが
@@toStringTag
または
@@toPrimitive
という名前のプロパティを持つことができるかどうかを示します。 リソースを
Symbol.toStringTag
検索のこのアプローチのおかげで、この検索では結果が得られないため、一般に回避できます。 この提案の実装により、SpiderMonkeyのアレイを使用したマイクロベンチマークのパフォーマンスが約2倍向上しました。
AngularJSの使用法をいくつか調べたので、このアイデアを見つけることができて非常に幸運だと感じ、V8で実装することにしました。 ソリューションのアーキテクチャについて考え始め、その結果、V8に移植しましたが、
Symbol.toStringTag
と
Object.prototype.toString()
限定されました。 実際、
Symbol.toPrimitive
がChromeまたはNode.jsの重要な問題の原因であるという証拠は見つかりませんでした(見つかったまで)。 ここでの主な考え方は、デフォルトでは、オブジェクトインスタンスに興味深い文字がないと想定し、インスタンスに新しいプロパティを追加するたびに、このプロパティの名前が類似した文字であるかどうかを確認することです。 その場合、オブジェクトインスタンスの非表示クラスに特定のビットを設定します。
const obj = {}; Object.prototype.toString.call(obj); // obj[Symbol.toStringTag] = 'a'; Object.prototype.toString.call(obj); //
この簡単な例を見てください。 次に、 興味深いシンボルを持たずに、
obj
オブジェクトが存在し始めます。 したがって、
Object.prototype.toString()
呼び出しは、
Object.prototype.toString()
の検索をスキップできる場合、新しい高速な実行パスに
Symbol.toStringTag
(これは、
Object.prototype
も興味深いシンボルがないため
Object.prototype
)。 2番目の呼び出しは、
obj
興味深い文字があるため、通常の低速検索操作を実行します。
最適化結果
V8でこのメカニズムを実装すると、上記のマイクロベンチマークのパフォーマンスが約5.8倍向上しました。 テストは、Linux、HP Z620ワークステーションで実施されました。 プロファイラーでパフォーマンスを確認すると、プログラムが
GetPropertyStub
時間を費やしていないことがわかります。 代わりに、
Object.prototype.toString()
メソッドにより、予想どおりシステムの主な負荷が作成されます。
![](https://habrastorage.org/getpro/habr/post_images/c8f/549/6d6/c8f5496d6d9977dd8974922e36b75744.png)
プロファイラーを使用してMozillaで開発されたマイクロベンチマークの研究(最適化後)
また、 ベンチマークに基づいて最適化されたエンジンをテストしましたが、これは少し現実に近いものです。 パフォーマンスの測定中に、
Object.prototype.toString()
は、特別に設定された
Symbol.toStringTag
プロパティを持つプリミティブやオブジェクトを含むさまざまな値を
Symbol.toStringTag
ます。 その結果、V8の最新バージョンはV8 6.1より6.5倍高速でした。
![](https://habrastorage.org/getpro/habr/post_images/2db/365/da1/2db365da15c21a90fe9cc2d771c62195.png)
V8のさまざまなバージョンでの新しいマイクロベンチマークの結果
Speedometerブラウザーベンチマーク、特にAngularJSテストでの最適化の影響の測定では、すべてのテストで1%の速度の向上、AngularJSテストを実行すると3%の確実な増加が示されました。
![](https://habrastorage.org/getpro/habr/post_images/176/5aa/c37/1765aac3767f8e3ac6fd0c8c5d1617fc.png)
ベンチマーク速度計に対する最適化の影響
まとめ
Object.prototype.toString()
ような高度に最適化された組み込みJavaScript関数で
Object.prototype.toString()
、さらに最適化される可能性があります。 特に、上記の最適化により、生産性が最大6.5倍向上します。 SpeedometerベンチマークのAngularJSサブテストなど、さまざまなパフォーマンステストの結果を十分に深く掘り下げると、これを確認できます。
Jan de MoisとTom Schusterの研究と興味深いシンボルを使った素晴らしいアイデアに感謝します 。
WebKitで使用されるJavaScriptエンジンであるJavaScriptCoreは、オブジェクトインスタンスの非表示クラスの
Object.prototype.toString()
連続呼び出しの結果をキャッシュすることに注意してください(このキャッシュは、ES2015仕様がリリースされる前の2012年の初めに登場しました)。 これは非常に興味深い戦略ですが、その範囲は限られています(つまり、 Symbol.toPrimitiveやSymbol.hasInstanceなどの他の有名なシンボルに適用する場合は役に立ちません )。 さらに、プロトタイプチェーンの変更に対するタイムリーな応答を保証するために、非常に複雑なキャッシュ無効化ロジックが必要です。 そのため、少なくとも現時点では、V8のキャッシュベースのソリューションを支持しない選択をしました。
親愛なる読者! JavaScriptアプリケーションのプロファイルを作成していますか? V8に実装されているどの標準JSメカニズムに最適化が必要ですか?