15

バックグラウンド

私が管理しているプロジェクトでは、多くの古い ES6 より前のブラウザーではネイティブにサポートされていないマップ (文字列キーのみ) の代わりとして、null プロトタイプ オブジェクトを広範囲に使用しています。

基本的に、その場で null プロトタイプ オブジェクトを作成するには、次を使用します。

var foo = Object.create(null);

これにより、新しいオブジェクトには、この特定のユースケースには望ましくない「toString」、「constructor」、「__proto__」などの継承されたプロパティがないことが保証されます。

このパターンはコード内で複数回出現するため、プロトタイプが null プロトタイプを持ち、独自のプロパティを持たないオブジェクトを作成するコンストラクターを作成するというアイデアを思いつきました。

var Empty = function () { };
Empty.prototype = Object.create(null);

次に、独自または継承されたプロパティを持たないオブジェクトを作成するには、次を使用できます。

var bar = new Empty;

問題

パフォーマンスを改善するためにテストを書いたところObject.create、すべてのブラウザーで、アドホック プロトタイプを持つ追加のコンストラクターを使用する方法よりも、ネイティブ アプローチのパフォーマンスが予想外にはるかに遅いことがわかりました: http://jsperf.com/blank-object -作成

後者の方法は、前者の場合には発生しないユーザー定義のコンストラクターの呼び出しを伴うため、後者の方法は遅くなると予想していました。

このようなパフォーマンスの違いの原因は何でしょうか?

4

4 に答える 4

20

実行しているブラウザの特定のバージョンに大きく依存するものを調査しています。jsperf テストを実行すると、次のような結果が得られます。

  • Chrome 47new Emptyでは、63m ops/秒でObject.create(null)実行されますが、10m ops/秒で実行されます。

  • Firefox 39 ではnew Empty、733m ops/秒でObject.create(null)実行されますが、1,685m ops/秒で実行されます。

(上記の「m」は、数百万について話していることを意味します。)

それで、あなたはどれを選びますか?あるブラウザで最速の方法は、他のブラウザでは最も遅くなります。

これだけでなく、ここで見ている結果は、新しいブラウザーのリリースで変わる可能性が非常に高い. Object.create適切な例として、v8での実装を確認しました。2015 年 12 月 30 日まで、 の実装はObject.createJavaScript で書かれていましたが、最近コミットにより C++ 実装に変更されました。これが Chrome に導入されると、比較結果がObject.create(null)変化new Emptyします。

でもこれが全てじゃない...

Object.create(null)一種のマップ (疑似マップ) として使用されるオブジェクトを作成するために使用することの1 つの側面だけを見てきました。この擬似マップへのアクセス時間はどうですか? これは、ミスのパフォーマンスをチェックするテストと、ヒットのパフォーマンスをチェックするテストです。

  • Chrome 47 では、 で作成されたオブジェクトを使用すると、ヒット ケースとミス ケースの両方が 90% 高速になりますObject.create(null)

  • Firefox 39 では、ヒット ケースはすべて同じように実行されます。ミスケースに関しては、 で作成されたオブジェクトのパフォーマンスObject.create(null)が非常に優れているため、jsperf で ops/sec の数が "Infinity" と表示されます。

Firefox 39 で得られた結果は、私が実際に期待していたものです。JavaScript エンジンは、オブジェクト自体でフィールドをシークする必要があります。ヒットした場合、オブジェクトがどのように作成されたかに関係なく、検索は終了します。オブジェクト自体でフィールドを見つけられなかった場合、JavaScript エンジンはオブジェクトのプロトタイプをチェックインする必要があります。で作成されたオブジェクトの場合、Object.create(null)プロトタイプがないため、そこで検索を終了します。で作成されたオブジェクトの場合new Empty、プロトタイプがあり、JavaScript エンジンが検索する必要があります。

では、疑似マップの存続期間中、どのくらいの頻度で疑似マップが作成されるのでしょうか? どのくらいの頻度でアクセスされていますか? 非常に特殊な状況でない限り、マップは1 回作成する必要がありますが、何度もアクセスする必要があります。 したがって、ヒットとミスの相対的なパフォーマンスは、アプリケーションの全体的なパフォーマンスにとってより重要になり、オブジェクトを作成するさまざまな手段の相対的なパフォーマンスよりも重要になります。

これらの疑似マップからキーを追加および削除するパフォーマンスを確認することもでき、さらに詳しく知ることができます。繰り返しますが、キーを決して削除しないマップがある可能性があります (私はそれらのいくつかを持っています) ので、削除のパフォーマンスはあなたの場合には重要ではないかもしれません.

最終的に、アプリケーションのパフォーマンスを改善するためにプロファイリングする必要があるのは、システムとしてのアプリケーションです。このようにして、実際のアプリケーションにおけるさまざまな操作の相対的な重要性が結果に反映されます。

于 2016-01-05T12:44:29.690 に答える
5

パフォーマンスの違いは、ほとんどの JS エンジンでコンストラクター関数が高度に最適化されているという事実に関係しています。Object.create がコンストラクター関数ほど高速ではないという実際的な理由は実際にはありません。時間の経過とともに改善される可能性が高い実装に依存するものです。

そうは言っても、オブジェクトを作成するコストは途方もなく低いため、パフォーマンスに基づいてどちらかを選択するべきではないことがパフォーマンス テストで証明されています。これらのマップをいくつ作成していますか? テストで最も遅い Object.create の実装でさえ、1 秒あたり 8,000,000 を超えるオブジェクトを大量に処理しているため、何百万ものマップを作成するやむを得ない理由がない限り、最も明白な解決策を選択します。

さらに、あるブラウザの実装は、別の実装より文字通り数百倍も高速になる可能性があるという事実を考慮してください。この違いは、どちらを選択しても存在するため、Object.create とコンストラクターの間の小さな違いは、さまざまな実装のより広いコンテキスト内で関連する違いと実際に見なされるべきではありません。

最終的に、 Object.create(null) は明らかな解決策です。オブジェクトを作成するパフォーマンスがボトルネックになる場合は、コンストラクターの使用を検討することもできますが、それでもEmptyコンストラクターのようなものを使用する前に、別の方法を検討します。

于 2016-01-05T02:04:19.760 に答える
1

jsperf が壊れているため、この質問はほとんど無効です。何らかの理由で結果が歪められます。独自のマップ実装(整数に基づくもの)を作成していたときに、個人的に確認しました。

これら 2 つの方法にまったく違いはありません。

ところで、これは同じ構文で空のオブジェクトを作成する簡単な方法だと思います。

var EmptyV2 = function() { return Object.create(null); };

私は、これら 3 つのメソッドをいくらでも作成する時間を出力する小さな独自のテストを作成しました。

ここにあります:

<!DOCTYPE html>
<html>
    <head>
        <style>
            html
            {
                background-color: #111111;
                color: #2ECC40;
            }
        </style>
    </head>
    <body>
    <div id="output">

    </div>

    <script type="text/javascript">
        var Empty = function(){};
        Empty.prototype = Object.create(null);

        var EmptyV2 = function() { return Object.create(null); };

        var objectCreate = Object.create;

        function createEmpties(iterations)
        {           
            for(var i = 0; i < iterations; i++)
            {           
                var empty = new Empty();
            }
        }

        function createEmptiesV2(iterations)
        {       
            for(var i = 0; i < iterations; i++)
            {
                var empty = new EmptyV2();
            }
        }

        function createNullObjects(iterations)
        {       
            for(var i = 0; i < iterations; i++)
            {
                var empty = objectCreate(null);
            }
        }

        function addResult(name, start, end, time)
        {           
            var outputBlock = document.getElementsByClassName("output-block");

            var length = (!outputBlock ? 0 : outputBlock.length) + 1;
            var index = length % 3;

            console.log(length);
            console.log(index);

            var output = document.createElement("div");
            output.setAttribute("class", "output-block");
            output.setAttribute("id", ["output-block-", index].join(''));
            output.innerHTML = ["|", name, "|", " started: ", start, " --- ended: ", end, " --- time: ", time].join('');

            document.getElementById("output").appendChild(output);

            if(!index)
            {
                var hr = document.createElement("hr");
                document.getElementById("output").appendChild(hr);
            }
        }

        function runTest(test, iterations)
        {
            var start = new Date().getTime();

            test(iterations);

            var end = new Date().getTime();

            addResult(test.name, start, end, end - start);
        }

        function runTests(tests, iterations)
        {
            if(!tests.length)
            {
                if(!iterations)
                {
                    return;
                }

                console.log(iterations);

                iterations--;

                original = [createEmpties, createEmptiesV2, createNullObjects];

                var tests = [];

                for(var i = 0; i < original.length; i++)
                {
                    tests.push(original[i]);
                }
            }

            runTest(tests[0], 10000000000/8);

            tests.shift();

            setTimeout(runTests, 100, tests, iterations);
        }

        runTests([], 10);
    </script>
    </body>
</html>

すみません、ちょっと固いです。index.html に貼り付けて実行するだけです。このテスト方法は、jsperf よりもはるかに優れていると思います。

ここに私の結果があります:

|createEmpties| 開始: 1451996562280 --- 終了: 1451996563073 --- 時間: 793
|createEmptiesV2| 開始: 1451996563181 --- 終了: 1451996564033 --- 時間: 852
|createNullObjects| 開始: 1451996564148 --- 終了: 1451996564980 --- 時間: 832


|createEmpties| 開始: 1451996565085 --- 終了: 1451996565926 --- 時間: 841
|createEmptiesV2| 開始: 1451996566035 --- 終了: 1451996566863 --- 時間: 828
|createNullObjects| 開始: 1451996566980 --- 終了: 1451996567872 --- 時間: 892

|createEmpties| 開始: 1451996567986 --- 終了: 1451996568839 --- 時間: 853
|createEmptiesV2| 開始: 1451996568953 --- 終了: 1451996569786 --- 時間: 833
|createNullObjects| 開始: 1451996569890 --- 終了: 1451996570713 --- 時間: 823

|createEmpties| 開始: 1451996570825 --- 終了: 1451996571666 --- 時間: 841
|createEmptiesV2| 開始: 1451996571776 --- 終了: 1451996572615 --- 時間: 839
|createNullObjects| 開始: 1451996572728 --- 終了: 1451996573556 --- 時間: 828

|createEmpties| 開始: 1451996573665 --- 終了: 1451996574533 --- 時間: 868
|createEmptiesV2| 開始: 1451996574646 --- 終了: 1451996575476 --- 時間: 830
|createNullObjects| 開始: 1451996575582 --- 終了: 1451996576427 --- 時間: 845

|createEmpties| 開始: 1451996576535 --- 終了: 1451996577361 --- 時間: 826
|createEmptiesV2| 開始: 1451996577470 --- 終了: 1451996578317 --- 時間: 847
|createNullObjects| 開始: 1451996578422 --- 終了: 1451996579256 --- 時間: 834

|createEmpties| 開始: 1451996579358 --- 終了: 1451996580187 --- 時間: 829
|createEmptiesV2| 開始: 1451996580293 --- 終了: 1451996581148 --- 時間: 855
|createNullObjects| 開始: 1451996581261 --- 終了: 1451996582098 --- 時間: 837

|createEmpties| 開始: 1451996582213 --- 終了: 1451996583071 --- 時間: 858
|createEmptiesV2| 開始: 1451996583179 --- 終了: 1451996583991 --- 時間: 812
|createNullObjects| 開始: 1451996584100 --- 終了: 1451996584948 --- 時間: 848

|createEmpties| 開始: 1451996585052 --- 終了: 1451996585888 --- 時間: 836
|createEmptiesV2| 開始: 1451996586003 --- 終了: 1451996586839 --- 時間: 836
|createNullObjects| 開始: 1451996586954 --- 終了: 1451996587785 --- 時間: 831

|createEmpties| 開始: 1451996587891 --- 終了: 1451996588754 --- 時間: 863
|createEmptiesV2| 開始: 1451996588858 --- 終了: 1451996589702 --- 時間: 844
|createNullObjects| 開始: 1451996589810 --- 終了: 1451996590640 --- 時間: 830

于 2016-01-05T12:43:01.883 に答える
0

パフォーマンスを改善するためにテストを書いたところ、すべてのブラウザーで、ネイティブの Object.create アプローチが、アドホック プロトタイプを使用する追加のコンストラクターを含むメソッドよりも予期せず大幅に遅くなることがわかりました。

後者の方法は、前者の場合には発生しないユーザー定義のコンストラクターの呼び出しを伴うため、後者の方法は遅くなると予想していました。

あなたの推論は、newオペレーターとObject.createが同じ内部「オブジェクト作成」コードを使用する必要があり、new. A+B を A と比較していると思うので、テスト結果が驚くべきものになるのはそのためです。

newしかし、それは真実ではありません。との実装についてあまり想定しないでくださいObject.create。どちらも異なる JS または「ネイティブ」(ほとんどが C++) に解決でき、カスタム コンストラクターはパーサーによって簡単に最適化できます。

好奇心に加えて、他の人がよく説明しているように、空のオブジェクトの作成は、アプリケーション全体を最適化するための悪い焦点です-そうでないことを証明するフルスケールのプロファイリングデータがない限り。

オブジェクトの作成時間が本当に心配な場合は、作成されたオブジェクトの数のカウンターを追加し、Emptyコンストラクターでインクリメントし、プログラムの有効期間中に作成されたオブジェクトの数をログに記録し、最も遅いブラウザーの実行時間を掛けて、 (おそらく) 作成時間がどれほど無視できるか。

于 2016-01-05T18:09:36.277 に答える