Почему arr = [] быстрее, чем arr = new Array?

146

Я запустил этот код и получил следующий результат. Мне интересно узнать, почему []это быстрее?

console.time('using[]')
for(var i=0; i<200000; i++){var arr = []};
console.timeEnd('using[]')

console.time('using new')
for(var i=0; i<200000; i++){var arr = new Array};
console.timeEnd('using new')
  • используя []: 299ms
  • используя new: 363ms

Благодаря Raynos, здесь есть эталон этого кода и еще один возможный способ определения переменной.

введите описание изображения здесь

Мохсен
источник
5
Вы можете быть заинтересованы в JSperf .
Заостренный
11
Тест
Raynos
Обратите внимание на ключевое слово new. Это означает «пожалуйста, будьте менее эффективны». Это никогда не имеет смысла, и требует, чтобы браузер выполнял обычную реализацию вместо попыток оптимизации.
beatgammit
2
@kinakuta нет. Они оба создают новые неравные объекты. Я имел в виду, что []это эквивалентно с new Array()точки зрения исходного кода, а не объектов, возвращаемых выражений формы
Raynos
1
Да, это не очень важно. Но мне нравится знать.
Мохсен

Ответы:

195

Более подробно о предыдущих ответах ...

С точки зрения общих компиляторов и без учета специфичных для ВМ оптимизаций:

Сначала мы проходим через фазу лексического анализа, где мы токенизируем код.

Например, могут быть получены следующие токены:

[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)

Надеюсь, это обеспечит вам достаточную визуализацию, чтобы вы могли понять, насколько требуется больше (или меньше) обработки.

  1. Основываясь на вышеупомянутых токенах, мы знаем, что ARRAY_INIT всегда будет создавать массив. Поэтому мы просто создаем массив и заполняем его. Что касается неоднозначности, то на этапе лексического анализа ARRAY_INIT уже отличается от средства доступа к свойству объекта (например obj[foo]) или скобок внутри строк / литералов регулярных выражений (например, "foo [] bar" или / [] /)

  2. Это незначительно, но у нас также есть больше жетонов new Array. Кроме того, еще не совсем ясно, что мы просто хотим создать массив. Мы видим «новый» токен, но «новый» что? Затем мы видим токен IDENTIFIER, который означает, что мы хотим новый «массив», но виртуальные машины JavaScript обычно не различают токен IDENTIFIER и токены для «нативных глобальных объектов». Следовательно...

  3. Мы должны искать цепочку областей действия каждый раз, когда встречаем токен IDENTIFIER. Виртуальные машины Javascript содержат «объект активации» для каждого контекста выполнения, который может содержать объект «аргументы», локально определенные переменные и т. Д. Если мы не можем найти его в объекте активации, мы начинаем искать цепочку областей действия, пока не достигнем глобальной области видимости. , Если ничего не найдено, мы бросаем ReferenceError.

  4. Как только мы нашли объявление переменной, мы вызываем конструктор. new Arrayявляется неявным вызовом функции, и практическое правило заключается в том, что вызовы функций медленнее во время выполнения (следовательно, почему статические компиляторы C / C ++ допускают «встраивание функций» - какие механизмы JS JIT, такие как SpiderMonkey, должны выполнять «на лету»)

  5. ArrayКонструктор перегружен. Конструктор Array реализован в виде нативного кода, поэтому он обеспечивает некоторые улучшения производительности, но ему все равно необходимо проверить длину аргументов и действовать соответствующим образом. Более того, если указан только один аргумент, нам необходимо дополнительно проверить тип аргумента. new Array ("foo") создает ["foo"], где new Array (1) создает [undefined]

Итак, чтобы упростить все это: с литералами массива, VM знает, что мы хотим массив; с new Array, виртуальная машина должна использовать дополнительные циклы процессора, чтобы выяснить, что на new Array самом деле делает.

Роджер Пун
источник
не a = новый массив (1000); для (от 0 до 999) {a [i] = i} быстрее, чем a = []; для (от 0 до 999) {a [i] = i} из-за накладные расходы хотя?
Y.
Только что сделал тестовый пример. new Array (n) работает быстрее в тех случаях, когда вы заранее знаете размер массива jsperf.com/square-braces-vs-new-array
Y.
27

Одна из возможных причин заключается в том, что new Arrayтребуется поиск имени Array(у вас может быть переменная с этим именем в области видимости), тогда как []это не так.

Хаммар
источник
4
Проверка аргументов также может помочь.
Леонид
Arrayисключает как один аргумент, так lenи несколько аргументов. Где как []принимает только несколько аргументов. Также тесты Firefox не показывают почти никакой разницы.
Рейнос
Я думаю, в этом есть доля правды. Тестирование петли OP в IIFE оказывает (относительно) существенное влияние на производительность. В том числе var Array = window.Arrayповышает производительность new Arrayтеста.
user113716 10.09.11
Я не думаю, что это правильно, потому что это console.time ('more vars new'); for (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); more vars new: 390ms и this console.time («больше vars new»); var myOtherObject = {}, myOtherArray = []; for (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); больше изменений new: 369ms Возвращается в то же время
Мохсен
2

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

Относительно небольшая разница в производительности подтверждает эту точку зрения. Кстати, вы можете сделать такой же тест с Object и object literal {}.

Лоран Зуйдвейк
источник
1

Это имело бы какой-то смысл

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

http://www.dyn-web.com/tutorials/obj_lit.php

lnguyen55
источник
1

Также интересно, что если длина массива известна заранее (элементы будут добавлены сразу после создания), использование конструктора массива указанной длины намного быстрее в недавнем Google Chrome 70+.

  • " новый массив ( % ARR_LENGTH% ) " - 100% (быстрее) !

  • « [] » - 160-170% (медленнее)

Диаграмма с результатами мероприятий.

Тест можно найти здесь - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

Примечание: этот результат проверен на Google Chrome v.70 + ; в Firefox v.70 и IE оба варианта практически равны.

Олег Заревенный
источник