#javascript #arrays #micro-optimization
#javascript #массивы #микрооптимизация
Вопрос:
Я всегда предполагал, что кэширование длины массива в JavaScript является хорошей идеей (особенно в состоянии for
цикла) из-за дороговизны вычисления длины массива.
Пример
for (var i = 0; i < arr.length; i ) { }
// vs
for (var i = 0, arrLength = arr.length; i < arrLength; i ) { }
Однако я подумал, что, возможно, length
свойство обновляется только при создании и изменении массива. Следовательно, его чтение не должно быть слишком дорогостоящей операцией в отличие от чтения его, хранящегося в переменной (в отличие от других методов на других языках, которым может потребоваться поиск в памяти, чтобы найти конец чего-либо, например strlen()
в C).
У меня есть два вопроса. Мне также интересно, как это работает, поэтому, пожалуйста, не обвиняйте меня в преждевременной оптимизации.
Предположим, что движки JavaScript в браузерах.
- Есть ли какое-либо преимущество в кэшировании
length
свойства массива в JavaScript? Намного ли сложнее читать локальную переменную поверх свойства объекта? length
Свойство просто изменяется при создании и в методах типаshift()
иpop()
, которые не возвращают новый массив и в противном случае просто сохраняются как целое число?
Ответ №1:
Ну, я бы сказал, что это дорого, но затем я написал небольшой тест @ jsperf.com и, к моему удивлению, использование i<array.length
на самом деле было быстрее в Chrome, а в FF (4) это не имело значения.
Я подозреваю, что длина хранится как целое число (Uint32). Из ECMA-specs (262 изд. 5, стр. 121):
Каждый объект массива имеет свойство length, значением которого всегда является неотрицательное целое число, меньшее 232. Значение свойства length численно больше, чем имя каждого свойства, имя которого является индексом массива; всякий раз, когда создается или изменяется свойство объекта Array, другие свойства корректируются по мере необходимости для поддержания этого инварианта. В частности, всякий раз, когда добавляется свойство, имя которого является индексом массива, свойство length при необходимости изменяется на единицу больше числового значения этого индекса массива; и всякий раз, когда изменяется свойство length, каждое свойство, имя которого является индексом массива, значение которого не меньше новой длины, автоматически удаляется. Это ограничение применяется только к собственным свойствам объекта Array и не зависит от свойств длины или индекса массива, которые могут быть унаследованы от его прототипов
Уф! Я не знаю, привыкну ли я когда-нибудь к такому языку…
Наконец, у нас всегда есть наш старый добрый браузер, который отстает. В IE (9, 8, 7) кэширование длины происходит действительно быстрее. Я говорю, что это одна из многих других причин не использовать IE.
Комментарии:
1. В моем Chrome 10.0.648.127 на Linux i686 это также не имело значения.
2. Массивы в языках сценариев (Ruby, Perl, …) обычно знают две длины в виде простых целых чисел: сколько слотов было выделено и сколько слотов используется (т. Е. Длина). Любой, кто сделал бы это по-другому в движке JavaScript, вероятно, не должен быть допущен к компилятору 🙂 Массивы обычно знают, сколько у них времени, не вычисляя его. За исключением C, конечно.
3. @Joachim Sauer. Предположим, что это правда. В любом случае, почему написание простого, понятного кода должно быть заявлено в качестве правила Java?
4. Это не просто правило Java, но в Java оно иногда цитируется по соображениям производительности . Есть часто цитируемая статья под названием «Write Dumb Code» .
5. Я бы сказал, что, поскольку кэширование длины действительно имеет значение в IE, а IE (по крайней мере, до IE 9) является чрезвычайно медленным браузером, такая оптимизация может быть полезной.
Ответ №2:
TL; DR:
Из того, что я могу собрать, кажется, что длина массива кэшируется внутри (по крайней мере, в версии 8)..
(Подробности? Читайте дальше :))
Итак, этот вопрос несколько раз крутился у меня в голове, и я решил добраться до корня проблемы (по крайней мере, в одной реализации).
Копаясь в исходном коде версии 8, мы получили класс JsArray.
// The JSArray describes JavaScript Arrays
// Such an array can be in one of two modes:
// - fast, backing storage is a FixedArray and length <= elements.length();
// Please note: push and pop can be used to grow and shrink the array.
// - slow, backing storage is a HashTable with numbers as keys.
Я делаю предположение, что тип элементов массива определяет, будет ли это быстро или медленно. Я добрался до того, что в set_has_fast_elements
( set_bit_field2(bit_field2() | (1 << kHasFastElements))
) был установлен флаг bit, в котором я решил нарисовать линию поиска, поскольку я искал в Google code и не имел локального источника.
Теперь, кажется, что в любое время с массивом выполняется любая операция (которая является дочерним классом JSObject
, выполняется вызов NormalizeElements()
, который выполняет следующее:
// Compute the effective length.
int length = IsJSArray() ?
Smi::cast(JSArray::cast(this)->length())->value() :
array->length();
Итак, отвечая на ваши вопросы:
- Похоже, что в Chrome (или других браузерах, использующих V8) нет никаких преимуществ в кэшировании
length
свойства массива (если только вы не делаете что-то странное, что заставило бы это сделатьslow
(я не уверен, каковы эти условия) — сказав это, я, скорее всего, продолжу кэшироватьlength
, пока не получу возможность просмотреть все реализации браузера OS 😉 length
Свойство, похоже, изменяется после любой операции над объектом.
Редактировать:
Кстати, кажется, что «пустой» массив на самом деле выделяется для 4 элементов:
// Number of element slots to pre-allocate for an empty array.
static const int kPreallocatedArrayElements = 4;
Я не уверен, на сколько элементов увеличивается массив после превышения границ — я не копал так глубоко 🙂
Комментарии:
1. Типичные реализации увеличивают массив на постоянную величину, кратную размеру массива — обычно путем удвоения, утроения, учетверения и т.д. все они дают одинаковую амортизацию
O(1)
для вставки.2. @matt: спасибо — я бы тоже так подумал, но не знал наверняка, поскольку не копал глубже 🙂
3. Это один длинный tl; dr, но 1 за поиск исходного кода браузера.
Ответ №3:
Еще один набор тестов производительности. Цикл выполняется над массивом из миллионов случайных чисел с пустым циклом.
В Chrome циклы с кэшированными и некэшированными длинами синхронизируются практически в одно и то же время, поэтому я предполагаю, что это оптимизация для кэширования длины с помощью V8.
В Safari и Firefox кэшированная длина неизменно была примерно в 2 раза быстрее, чем некэшированная версия.
Комментарии:
1. В Firefox 40 кэшированная и некэшированная версии имеют одинаковую производительность.
Ответ №4:
В этой статье исследуется автоматическое кэширование в V8 и Chrome, запрашивая у IRHydra сгенерированный код:
Как Гринч украл доступ к array.length Вячеслава Егорова
Он обнаружил, что при определенных условиях ручное кэширование .length
фактически увеличивает накладные расходы, а не улучшает производительность!
Но в любом случае, такого рода микрооптимизация вряд ли приведет к какой-либо заметной выгоде для ваших пользователей. Для их пользы и для вашей, вместо этого сосредоточьтесь на коде, который понятен для чтения, и используйте в своем коде хорошие структуры данных и алгоритмы!
Избегайте преждевременной оптимизации: сосредоточьтесь на элегантном коде, пока не возникнет проблема с производительностью. Только после этого найдите узкое место с помощью профилирования, а затем оптимизируйте только эту часть кода.
Ответ №5:
Просто примечание:
В некоторых браузерах (я заметил это в Safari, IE и Opera) вы можете увеличить скорость, кэшируя длину внутри объявления цикла for:
var j;
for (var i = 0, len = arr.length; i < len; i ) {
j = arr[i];
}
Я отредактировал приведенный выше тест jsperf @KooiInc, чтобы добавить этот случай.
Комментарии:
1. Из интереса, я только что провел этот тест в основных четырех браузерах (теперь включенных в диаграмму), и сохранение длины является довольно заметным плюсом в IE и Safari, но это не дает никакой пользы в Chrome и Firefox. Удивительно, но Firefox поразил другие браузеры. Неудивительно, что Safari был самым медленным в моем тестировании.
Ответ №6:
Будьте осторожны, чтобы не предполагать, что это верно для всех повторяющихся коллекций. Например, кэширование длины HTMLCollection выполняется на 65% быстрее в Chrome (версия 41) и на 35% быстрее в Firefox (версия 36).
Комментарии:
1. Также важно отметить, что
length
не является статичным в HTMLCollection, оно будет настраиваться для отражения сопоставленных элементов.