Почему-это объект.создавать настолько медленнее, чем конструктор?



Фон



В проекте, который я поддерживаю, мы широко используем нулевые прототипные объекты как плохую альтернативу картам (только для строковых ключей), которые изначально не поддерживаются во многих старых браузерах до ES6.



В принципе, чтобы создать нулевой прототип объекта на лету, можно было бы использовать:



var foo = Object.create(null);


Это гарантирует, что новый объект не имеет наследуемых свойств, таких как "toString", "конструктор", "__proto__", которые нежелательны для данного конкретного использования. дело.

Поскольку этот шаблон появляется в коде несколько раз, нам пришла в голову идея написать конструктор, который создавал бы объекты, прототип которых имеет нулевой прототип и не имеет собственных свойств.

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


Тогда для создания объекта без собственных или унаследованных свойств можно использовать:



var bar = new Empty;


Проблема



Стремясь улучшить производительность, я написал тест и обнаружил, что собственный подход Object.create неожиданно работает намного медленнее, чем метод, включающий дополнительный конструктор со специальным прототипом, во всех браузерах: http://jsperf.com/blank-object-creation .



Я наивно ожидал, что последний метод будет медленнее, поскольку он включает вызов определяемого пользователем конструктора, чего не происходит в первом случае.

Что может быть причиной такой разницы в производительности?
576   4  

4 ответов:

Вы исследовали что-то, что сильно зависит от конкретной версии браузера, который вы запускаете. Вот некоторые результаты, которые я получаю здесь, когда я запускаю ваш тест jsperf:

  • В Chrome 47 new Empty работает со скоростью 63m ops/sec, тогда как Object.create(null) работает со скоростью 10m ops/sec.

  • В Firefox 39 new Empty работает со скоростью 733 м ops/сек, тогда как Object.create(null) работает со скоростью 1685 м ops/сек.

("м" выше означает, что мы говорим о миллионах.)

Так кто же из вас выбрать? Метод, который является самым быстрым в одном браузере, является самым медленным в другом.

Не только это, но и результаты, которые мы рассматриваем здесь, очень вероятно, изменятся с новыми выпусками браузера. В данном случае я проверил реализацию Object.create в v8. До 30 декабря 2015 года реализация Object.create была написана на JavaScript, ноcommit недавно изменил ее на реализацию C++. После того, как это делает свой путь в Chrome, результаты сравнения Object.create(null) и new Empty будут меняться.

Но это еще не все...

Вы рассмотрели только один аспект использования Object.create(null) для создания объекта, который будет использоваться в качестве своего рода карты (псевдокарты). Насчет времени доступа в эту псевдо-карту? Вот тест, который проверяет производительность промахов и один, который проверяет производительность ударов.

  • В Chrome 47 случаи попадания и промаха на 90% быстрее при создании объекта с Object.create(null).

  • В Firefox 39 все случаи попадания выполняются одинаково. Что касается случаев промаха, то производительность объекта, созданного с помощью Object.create(null), настолько велика, что jsperf говорит мне, что число операций/сек равно "бесконечности".

Результаты, полученные с Firefox 39, являются теми, которые я действительно ожидал. Движок JavaScript должен искать поле в самом объекте. Если это попадание, то поиск окончен, независимо от того, как был создан объект. Если есть промах на найдя поле в самом объекте, движок JavaScript должен проверить прототип объекта. В случае объектов, созданных с помощью Object.create(null), прототипа нет, поэтому поиск на этом заканчивается. В случае объектов, созданных с помощью new Empty, существует прототип, в котором движок JavaScript должен выполнить поиск.

Теперь, во время жизни псевдокарты, как часто создается эта псевдокарта? Как часто к нему обращаются? Если вы не находитесь в действительно необычной ситуации, карта должна быть создан один раз, но доступен много раз. таким образом, относительная производительность попаданий и промахов будет более важна для общей производительности вашего приложения, чем относительная производительность различных средств создания объекта. Мы также могли бы посмотреть на производительность добавления и удаления ключей из этих псевдо-карт, и мы узнали бы больше. Опять же, возможно, у вас есть карты, с которых вы никогда не удаляете ключи (у меня есть несколько таких), поэтому удаление производительность может не иметь значения для вашего случая.

В конечном счете, то, что вы должны профилировать для повышения производительности вашего приложения, - это ваше приложение как система.Таким образом, относительная важность различных операций в вашем фактическом применении будет отражена в ваших результатах.

Разница в производительности связана с тем, что конструкторские функции сильно оптимизированы в большинстве движков JS. На самом деле нет никакой практической причины для этого объекта.создание не может быть столь же быстрым, как функции конструктора, это просто зависящая от реализации вещь, которая, вероятно, улучшится с течением времени.

Как говорится, все тесты производительности доказывают, что вы не должны выбирать одно или другое на основе производительности, потому что стоимость создания объекта смехотворна низкий. Сколько таких карт вы создаете? Даже самая медленная реализация объекта.create on the tests по-прежнему выбрасывает более 8 000 000 объектов в секунду, поэтому, если у вас нет веских причин создавать миллионы карт, я бы просто выбрал наиболее очевидное решение.

Кроме того, рассмотрим тот факт, что одна реализация браузера может быть буквально в 100 раз быстрее, чем другая реализация. Эта разница будет существовать независимо от того, какой вы выберете, поэтому маленький различие между объектами.create и конструкторы на самом деле не должны рассматриваться как существенная разница в более широком контексте различных реализаций.

В Конечном Счете, Объект.создать(нуль) является очевидным решением. Если производительность создания объектов становится узким местом, то , возможно, рассмотрит возможность использования конструкторов, но даже тогда я бы посмотрел в другом месте, прежде чем прибегать к использованию чего-то вроде Empty конструкторов.

Этот вопрос в значительной степени недействителен, потому что jsperf сломан, он искажает результаты по любой причине. Я лично проверил это, когда делал свою собственную реализацию карты (основанную на целых числах).

Есть чисто никакой разницы между этими двумя методами.

Кстати, я думаю, что это более простой способ создать пустой объект с тем же синтаксисом:

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>

Извините, он немного жесткий. Просто вставьте его в индекс.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

Стремясь улучшить производительность, я написал тест и обнаружил, что собственный объект.создать подход неожиданно работает намного медленнее, чем метод, включающий дополнительный конструктор с нерегламентированным прототипом, в все браузеры

Я простодушно ожидал, что последний метод будет медленнее, поскольку он включает в себя вызов определяемого пользователем конструктора, который не происходит в первый случай.

Ваше рассуждение постулирует, что оператор new и оператор Object.create имеют использовать тот же внутренний код "создания объекта", с дополнительным вызовом пользовательского конструктора для new. Вот почему вы находите результат теста удивительным, потому что вы думаете, что сравниваете A+B с A.

Но это не так, вы не должны так много предполагать о реализациях new и Object.create. Оба могут быть разрешены в разные JS или "родной" (в основном C++), и ваш пользовательский конструктор может быть легко оптимизирован синтаксическим анализатором.

Помимо любопытства, как хорошо объяснили другие, создание пустого объекта является плохой точкой фокусировки для оптимизации всего приложения-если только у вас нет каких-либо полномасштабных данных профилирования, доказывающих обратное.

Если Выдействительно обеспокоены временем создания объекта, добавьте счетчик для количества созданных объектов, увеличьте его в своем конструкторе Empty, запишите количество объектов, созданных за время существования программы, умножьте на самое медленное время выполнения браузера и посмотрите (скорее всего), насколько ничтожно время создания является.

Comments

    Ничего не найдено.