Какова производительность объектов/массивов в JavaScript? (специально для Google V8)
производительность, связанная с массивами и объектами в JavaScript (особенно Google V8) было бы очень интересно документировать. Я не нахожу исчерпывающей статьи на эту тему нигде в Интернете.
Я понимаю, что некоторые объекты используют классы в качестве своей базовой структуры данных. Если есть много свойств, это иногда рассматривается как хэш-таблица?
Я также понимаю, что массивы иногда обрабатываются как массивы C++ (т. е. быстрая случайная индексация, медленная удаление и изменение). И, в других случаях, они обрабатываются больше как объекты (быстрая индексация, быстрая вставка/удаление, больше памяти). И, возможно, иногда они хранятся в виде связанных списков (т. е. медленное случайное индексирование, быстрое удаление/вставка в начале/конце)
какова точная производительность извлечения массива / объекта и манипуляций в JavaScript? (специально для Google V8)
более конкретно, какое это влияние на производительность оф:
- добавление свойства к объекту
- удаление свойства из объекта
- индексирование свойства в объекте
- добавление элемента в массив
- удаление элемента из массива
- индексирование элемента в массиве
- Вызове Array.pop ()
- Вызове Array.толкать()
- Вызове Array.shift ()
- Вызове Array.unshift ()
- вызов Матрица.slice ()
любые статьи или ссылки для более подробной информации будут оценены, как хорошо. :)
EDIT: мне действительно интересно, как JavaScript массивы и объекты работают под капотом. Кроме того, в чем контекст двигатель V8 "знает", чтобы "переключиться" на другую структуру данных?
например, предположим, что я создаю массив...
var arr = [];
arr[10000000] = 20;
arr.push(21);
что здесь происходит?
или... Что о этот...???
var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
var item = arr[i].shift();
//Do something with item...
}
для обычных массивов производительность была бы ужасной; тогда как, если бы использовался LinkedList... не так уж и плохо.
4 ответа:
обновление: обратите внимание, что JSPref в настоящее время не работает
(я сохранил копию тестового случая и обновлю ответ, как только jspref будет исправлен / найден преемник)
Мда... может быть, излишек для ответа... но я создал набор тестов, именно для изучения этих вопросов (и многое другое) (архивная копия).
и в этом смысле, вы можете увидеть проблемы с производительностью в этом тестере 50 + test case (это займет много времени).
кроме того, как следует из названия, он исследует использование собственного связанного списка структуры DOM.
(в настоящее время вниз, перестроен в процессе) более подробная информация об этом в моем блоге.
резюме выглядит следующим образом
- V8 массив быстро, очень быстро
- массив push / pop / shift ~прибл 20x + быстрее, чем любой эквивалент объекта.
- удивительно
Array.shift()
быстро ~приблизительно в 6 раз медленнее, чем массив pop, но ~приблизительно в 100 раз быстрее, чем удаление атрибута объекта.- забавно,
Array.push( data );
быстрееArray[nextIndex] = data
почти в 20 (динамический массив) до 10 (фиксированный массив) раз.Array.unshift(data)
медленнее, как и ожидалось, и ~приблизительно в 5 раз медленнее, чем добавление нового свойства.- обнуление значения
array[index] = null
быстрее, чем удалить егоdelete array[index]
(неопределенный) в массиве на ~приблизительно 4x++ быстрее.- удивительно обнуление значения в объекте
obj[attr] = null
~приблизительно в 2 раза медленнее, чем просто удаление атрибутаdelete obj[attr]
- неудивительно, что mid array
Array.splice(index,0,data)
медленно, очень медленно.- удивительно,
Array.splice(index,1,data)
была оптимизирована (без изменения длины) и в 100 раз быстрее, чем просто соединениеArray.splice(index,0,data)
- неудивительно, что divLinkedList уступает массиву на всех секторах, кроме
dll.splice(index,1)
удаления (Где он сломал тестовую систему).- САМЫЙ БОЛЬШОЙ СЮРПРИЗ из всего этого [как указал jjrv], записи массива V8 немного быстрее, чем чтение V8 =O
Примечание: эти метрики применяются только к большим массивам/объектам, которые v8 не "полностью оптимизирует". Могут быть очень изолированные оптимизированные случаи производительности для размера массива / объекта меньше, чем произвольный размер (24?). Более подробную информацию можно увидеть в нескольких google IO видео.
примечание 2: эти замечательные результаты производительности не разделяются между браузерами, особенно
*cough*
IE. Также Тест огромен, поэтому мне еще предстоит полностью проанализировать и оценить результаты : пожалуйста, отредактируйте его в=)Обновлено Примечание (декабрь 2012): у представителей Google есть видео на youtubes, описывающие внутреннюю работу самого chrome (например, когда он переключается с массива linkedlist на фиксированный массив и т. д.) и как оптимизировать их. Смотрите GDC 2012: от консоли до Chrome дополнительные.
Обновлено Примечание (февраль 2013): Thx @badunk, для предоставления видеосвязи в точном месте
Обновлено Примечание (июнь 2016): спасибо @Бенедикт, относительно выбора производительности разница в фиксированные / динамические массивы.
на базовом уровне, который остается в пределах областей JavaScript, свойства объектов являются гораздо более сложными сущностями. Вы можете создавать свойства с помощью сеттеров / геттеров, с различной перечислимостью, возможностью записи и конфигурируемостью. Элемент в массиве не может быть настроена таким образом: он либо есть, либо его нет. На базовом уровне двигателя это позволяет гораздо более оптимизацию с точки зрения организации памяти, представляющей структуру.
С точки зрения идентифицируя массив из объекта (словаря), JS-движки всегда делали явные линии между ними. Вот почему существует множество статей о методах попытки сделать полу-поддельный массив, подобный объекту, который ведет себя как один, но допускает другие функции. Причина, по которой это разделение даже существует, заключается в том, что сами двигатели JS хранят их по-разному.
свойства могут храниться в объекте массива, но это просто демонстрирует, как JavaScript настаивает на создании все является объектом. Индексированные значения в массиве хранятся иначе, чем любые свойства, которые вы решили установить для объекта массива, представляющего базовые данные массива.
всякий раз, когда вы используете законный объект массива и используете один из стандартных методов манипулирования этим массивом, вы будете поражать базовые данные массива. В частности, в V8 они по существу совпадают с массивом C++, поэтому эти правила будут применяться. Если по какой-то причине вы работаете с массивом то, что двигатель не может с уверенностью определить, является массивом, тогда вы находитесь на гораздо более шаткой почве. С последними версиями V8 есть больше места для работы, хотя. Например, можно создать класс, который имеет массив.прототип как его прототип и все еще получить эффективный доступ к различным собственным методам манипуляции массивом. Но это недавнее изменение.
конкретные ссылки на последние изменения в манипуляции с массивами могут пригодиться здесь:
- http://code.google.com/p/v8/source/detail?r=10024
- http://code.google.com/p/v8/source/detail?r=9849
- http://code.google.com/p/v8/source/detail?r=9747
Как немного больше, вот Array Pop и Array Push непосредственно из источника V8, оба реализованы в самом JS:
function ArrayPop() { if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { throw MakeTypeError("called_on_null_or_undefined", ["Array.prototype.pop"]); } var n = TO_UINT32(this.length); if (n == 0) { this.length = n; return; } n--; var value = this[n]; this.length = n; delete this[n]; return value; } function ArrayPush() { if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) { throw MakeTypeError("called_on_null_or_undefined", ["Array.prototype.push"]); } var n = TO_UINT32(this.length); var m = %_ArgumentsLength(); for (var i = 0; i < m; i++) { this[i+n] = %_Arguments(i); } this.length = n + m; return this.length; }
во время работы в узле.js 0.10 (построенный на v8) я видел использование ЦП, которое казалось чрезмерным для рабочей нагрузки. Я проследил одну проблему производительности до функции, которая проверяла наличие строки в массиве. Поэтому я провел несколько тестов.
- загружено 90,822 хостов
- загрузка конфигурации заняла 0,087 секунды (массив)
- загрузка конфигурации заняла 0,152 секунды (объект)
загрузка 91k записей в массив (с validate & push) является быстрее, чем установка obj[key]=value.
в следующем тесте я искал каждое имя хоста в списке один раз (91k итераций, чтобы усреднить время поиска):
- поиск конфигурации занял 87.56 секунд (массив)
- поиск конфигурации занял 0,21 секунды (объект)
приложение здесь Haraka (SMTP-сервер), и он загружает host_list один раз при запуске (и после изменений), а затем выполняет этот поиск миллионы раз во время операция. Переход на объект был огромным выигрышем в производительности.
Я хотел бы дополнить существующие ответы исследованием вопроса о том, как реализации ведут себя в отношении растущих массивов: если они реализуют их "обычным" способом, можно было бы увидеть много быстрых толчков с редкими, перемежающимися медленными толчками, в которых реализация копирует внутреннее представление массива из одного буфера в более крупный.
вы можете увидеть этот эффект очень хорошо, это от Chrome:
16: 4ms 40: 8ms 2.5 76: 20ms 1.9 130: 31ms 1.7105263157894737 211: 14ms 1.623076923076923 332: 55ms 1.5734597156398105 514: 44ms 1.5481927710843373 787: 61ms 1.5311284046692606 1196: 138ms 1.5196950444726811 1810: 139ms 1.5133779264214047 2731: 299ms 1.5088397790055248 4112: 341ms 1.5056755767118273 6184: 681ms 1.5038910505836576 9292: 1324ms 1.5025873221216042
даже если каждый толчок профилированный, вывод содержит только те, которые занимают время выше определенного порога. Для каждого теста я настроил порог, чтобы исключить все толчки, которые, как представляется, представляют быстрые толчки.
таким образом, первое число представляет, какой элемент был вставлен (первая строка для 17-го элемента), второе-сколько времени это заняло (для многих массивов тест выполняется параллельно), а последнее значение-это деление первого числа на то, что в первом линия.
все строки, которые имеют время выполнения менее 2 мс, исключаются для Chrome.
вы можете видеть, что Chrome увеличивает размер массива в степени 1,5, плюс некоторое смещение для учета небольших массивов.
для Firefox, это сила двух:
126: 284ms 254: 65ms 2.015873015873016 510: 28ms 2.0078740157480315 1022: 58ms 2.003921568627451 2046: 89ms 2.0019569471624266 4094: 191ms 2.0009775171065494 8190: 364ms 2.0004885197850513
мне пришлось немного поднять порог в Firefox, поэтому мы начинаем с #126.
С IE, мы получаем смесь:
256: 11ms 256 512: 26ms 2 1024: 77ms 2 1708: 113ms 1.66796875 2848: 154ms 1.6674473067915691 4748: 423ms 1.6671348314606742 7916: 944ms 1.6672283066554338
это сила двух сначала, а затем он движется к полномочиям пяти третей.
поэтому все распространенные реализации используют "нормальный" способ для массивов (вместо того, чтобы сходить с ума с веревки, например).
вот тестовый код и вот скрипка это.
var arrayCount = 10000; var dynamicArrays = []; for(var j=0;j<arrayCount;j++) dynamicArrays[j] = []; var lastLongI = 1; for(var i=0;i<10000;i++) { var before = Date.now(); for(var j=0;j<arrayCount;j++) dynamicArrays[j][i] = i; var span = Date.now() - before; if (span > 10) { console.log(i + ": " + span + "ms" + " " + (i / lastLongI)); lastLongI = i; } }