- Оригинальный адрес:Javascript Array.push в 945 раз быстрее, чем Array.concat 🤯🤔
- Оригинальный автор:Shi Ling
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:Xuyuey
- Корректор:Цянь Цзюньин, MLS
Если вы хотите объединить массивы с тысячами элементов, используйтеarr1.push(...arr2)
сопоставимыйarr1 = arr1.concat(arr2)
сэкономить время. Если вы хотите работать еще быстрее, вы даже можете написать свою собственную функцию, которая выполняет объединенные массивы.
подожди... с.concat
Сколько времени занимает объединение 15000 массивов?
Недавно у нас был пользователь, жалующийся на то, что он используетUI-liciousПри тестировании их пользовательского интерфейса он был заметно медленнее. Обычно каждыйI.click
I.fill
I.see
Команды, выполнение которых занимает около 1 секунды (постобработка, например создание скриншотов), теперь выполняются более 40 секунд, поэтому тесты, которые обычно выполнялись за 20 минут, теперь выполняются часами, что серьезно замедляет их процесс развертывания.
Я быстро установил таймер и заблокировал часть кода, которая вызывала замедление, но я был очень удивлен, когда нашел виновника:
arr1 = arr1.concat(arr2)
массив.concat
метод.
Простые инструкции могут быть использованы для того, чтобы разрешить тестирование, напримерI.click("Login")
, вместо использования селекторов CSS или XPATH, таких какI.click("#login-btn")
, UI-licious использует динамический анализ кода (шаблоны) для анализа дерева DOM, чтобы определить, что тестировать и как тестировать веб-сайт, на основе семантики веб-сайта, свойств доступности и различных популярных, но нестандартных шаблонов. Эти.concat
Операции используются для выравнивания дерева DOM для анализа, но когда дерево DOM очень большое и очень глубокое, производительность ужасна, это то, что случилось с нашими пользователями, когда они недавно обновили свое приложение, и эта волна обновлений также вызвала их Страница заметно раздута (это проблема с производительностью на их стороне, другая тема).
использовать.concat
Потребовалось 6 секунд, чтобы объединить 15000 массивов со средним числом элементов 5.
Нани?
6 секунд...
Всего 15 000 массивов, а в среднем всего пять элементов?
Объем данных не велик.
Почему так медленно? Есть ли более быстрый способ объединить массивы?
Сравнительное сравнение
.push против .concat, объединить 10000 массивов из 10 элементов
Итак, я начал исследовать (я имею в виду гугление).concat
Сравнение с другими способами объединения массивов в Javascript.
Оказывается, самый быстрый способ объединить массивы — это использовать.push
метод, который может принимать n аргументов:
// 将 arr2 的内容压(push)入 arr1 中
arr1.push(arr2[0], arr2[1], arr2[3], ..., arr2[n])
// 由于我的数组大小不固定,我使用了 `apply` 方法
Array.prototype.push.apply(arr1, arr2)
Это быстрее по сравнению, прыжок.
Как быстро?
Я сам провел несколько тестов производительности, чтобы убедиться в этом. Вуаля, вот разница в исполнении в Chrome:
Merge имеет массив размером 10 10000 раз,.concat
составляет 0,40 опс/сек (операций в секунду), а.push
Скорость 378 опс/сек. то естьpush
Сравниватьconcat
Полный в 945 раз быстрее! Эта разница может не быть линейной, но на таком небольшом количестве данных уже очевидна.
В Firefox результаты выполнения следующие:
В целом, движок Firefox SpiderMonkey Javascript медленнее по сравнению с движком Chrome V8, но.push
по-прежнему занимает первое место, болееconcat
в 2260 раз быстрее.
Мы внесли вышеуказанные изменения в код, и это устранило все замедление.
.push против .concat, объединить 2 массива с 50000 элементами
Но что, если вместо 10 000 массивов по 10 элементов объединить два огромных массива по 50 000 элементов?
Вот результаты тестирования в Chrome:
.push
еще лучше, чем.concat
Быстрее, но на этот раз в 9 раз.
945x не сильно медленнее, но довольно медленно.
Более изящные операции расширения
если ты чувствуешьArray.prototype.push.apply(arr1, arr2)
Это многословно, вы можете сделать простое преобразование, используя оператор распространения ES6:
arr1.push(...arr2)
Array.prototype.push.apply(arr1, arr2)
а такжеarr1.push(...arr2)
Разница в производительности между ними практически незначительна.
но почемуArray.concat
так медленно?
Это во многом связано с движком Javascript, и я не знаю точного ответа, поэтому я спросил своего друга@picocreator——GPU.jsСооснователь , потративший много времени на изучение исходного кода V8. Потому что у моего MacBook недостаточно памяти для запуска.concat
Объединить два массива длиной 50000,@picocreatorТакже одолжил мне детский игровой ПК, который он использовал для тестирования GPU.js для запуска тестов JsPerf.
Очевидно, что ответ во многом зависит от того, как они работают: при слиянии массивов.concat
создает новый массив и.push
Просто изменяет первый массив. Эти дополнительные операции (добавление элементов первого массива в возвращаемый массив) выполняются медленно..concat
Ключ к скорости.
Я: "Нани? Невозможно? И все? Но почему такой большой разрыв? Невозможно!" @picocreator: «Я не шучу, попробуйте написать собственные реализации .concat и .push, и вы все узнаете!»
Так что я попробовал в соответствии с тем, что он сказал, написал несколько методов реализации и добавил иlodashиз_.concat
Сравнение:
Родная реализация 1
Давайте обсудим первый набор нативных реализаций:
.concat
нативная реализация
// 创建结果数组
var arr3 = []
// 添加 arr1
for(var i = 0; i < arr1Length; i++){
arr3[i] = arr1[i]
}
// 添加 arr2
for(var i = 0; i < arr2Length; i++){
arr3[arr1Length + i] = arr2[i]
}
.push
нативная реализация
for(var i = 0; i < arr2Length; i++){
arr1[arr1Length + i] = arr2[i]
}
Как видите, единственная разница между ними состоит в том,.push
Первый массив модифицируется непосредственно в реализации.
Результат обычного метода реализации:
-
.concat
: 75 ops/sec -
.push
: 793 операции/сек (в 10 раз быстрее)
Результат нативной реализации метода 1:
-
.concat
: 536 ops/sec -
.push
: 11 104 операций в секунду (в 20 раз быстрее)
Получается, что я сам это написалconcat
а такжеpush
Быстрее, чем их обычные реализации... но мы видим, что простое создание нового массива и копирование в него содержимого первого массива может значительно замедлить весь процесс.
Собственная реализация 2 (предварительно выделить окончательный размер массива)
Мы можем еще больше улучшить нативную реализацию, предварительно выделив размер массива перед добавлением элементов, что может иметь огромное значение.
с заранее выделенным.concat
нативная реализация
// 创建结果数组并给它预先分配大小
var arr3 = Array(arr1Length + arr2Length)
// 添加 arr1
for(var i = 0; i < arr1Length; i++){
arr3[i] = arr1[i]
}
// 添加 arr2
for(var i = 0; i < arr2Length; i++){
arr3[arr1Length + i] = arr2[i]
}
с заранее выделенным.push
нативная реализация
// 预分配大小
arr1.length = arr1Length + arr2Length
// 将 arr2 的元素添加给 arr1
for(var i = 0; i < arr2Length; i++){
arr1[arr1Length + i] = arr2[i]
}
Результат нативной реализации метода 1:
-
.concat
: 536 ops/sec -
.push
: 11 104 операций в секунду (в 20 раз быстрее)
Результат нативной реализации метода 2:
-
.concat
: 1,578 ops/sec -
.push
: 18 996 операций в секунду (в 12 раз быстрее)
Предварительное выделение размера конечного массива может повысить производительность каждого метода в 2-3 раза.
.push
массив против..push
один элемент
Что, если мы будем отправлять только один элемент за раз? это будет лучше, чемArray.prototype.push.apply(arr1, arr2)
быстро?
for(var i = 0; i < arr2Length; i++){
arr1.push(arr2[i])
}
результат
-
.push
Весь массив: 793 операции/сек. -
.push
Одиночный элемент: 735 операций в секунду (медленно)
так.push
один элемент, чем.push
Весь массив работает медленно, что также имеет смысл.
Вывод: почему.push
Сравнивать.concat
Быстрее
в общем,concat
Сравнивать.push
Основная причина для замедления так много, что она создает новый массив, вам также необходимо скопировать дополнительный элемент массива на новый массив.
Теперь для меня еще одна загадка...
еще один фанат
Почему обычная реализация медленнее нативной? 🤔 Я снова@picocreatorИскать помощи.
Мы посмотрели на Лодаш_.concat
реализации, хотите получить некоторую информацию о.concat
Подсказки для обычных реализаций, так как они сопоставимы по производительности (lodash немного быстрее).
Факты доказали, что согласно.concat
Спецификация общей реализации, этот метод перегружен и поддерживает два метода передачи параметров:
- Передайте n значений для добавления в качестве аргументов, например:
[1,2].concat(3,4,5)
- Передайте массив для слияния в качестве параметра, например:
[1,2].concat([3,4,5])
Вы даже можете написать:[1,2].concat(3,4,[5,6])
Lodash выполняет ту же перегрузку и поддерживает два способа передачи параметров: lodash помещает все параметры в массив, а затем сглаживает его. Так что имеет смысл, если вы передаете ему несколько массивов. Но когда вы передаете массив, который необходимо объединить, он не просто использует сам массив, а копирует его в новый массив, а затем сглаживает.
……Отлично……
Таким образом, вы определенно можете оптимизировать производительность. Вот почему вы хотите реализовать объединенные массивы самостоятельно.
Кроме того, это только я и@picocreatorОсновываясь на исходном коде Lodash и его немного устаревшем знании исходного кода V8,.concat
Понимание того, как работает общая реализация движка.
Вы можете нажать в свободное времяздесьПрочтите исходный код lodash.
Дополнительные инструкции
-
В наших тестах использовались только массивы, содержащие целые числа. Все мы знаем, что движки Javascript могут выполняться быстрее, используя массивы определенных типов. Если в массиве есть объекты, ожидается, что результат будет медленнее.
-
Вот характеристики ПК, на котором выполнялся тест:
Почему мы выполняем операции с такими большими массивами во время тестов, ориентированных на пользовательский интерфейс?
Принцип работы механизма тестирования с учетом пользовательского интерфейса сканирует DOM-дерево целевого приложения, оценивая семантику, доступные атрибуты и другие распространенные шаблоны для определения целевых элементов и методов тестирования.
Таким образом, мы можем гарантировать, что тесты будут написаны так же просто, как:
// 跳转到 dev.to
I.goTo("https://dev.to")
// 在搜索框进行输入和搜索
I.fill("Search", "uilicious")
I.pressEnter()
// 我应该可以看见我自己和我的联合创始人
I.see("Shi Ling")
I.see("Eugene Cheah")
Селекторы CSS или XPATH не используются, что делает тесты более читабельными, менее чувствительными к изменениям в пользовательском интерфейсе и более простыми в обслуживании.
Примечание. Объявления общественных служб. Пожалуйста, держите DOM небольшим!
К сожалению, по мере того, как люди используют современные интерфейсные фреймворки для создания все более сложных и динамичных приложений, существует тенденция к тому, что деревья DOM становятся все больше и больше. Фреймворки — это палка о двух концах, которая позволяет нам развиваться быстрее, но люди часто забывают, какой багаж они добавляют. При изучении исходного кода различных веб-сайтов меня часто пугает количество элементов, которые существуют исключительно для того, чтобы оборачивать другие элементы.
Если вы хотите узнать, не слишком ли много узлов DOM на вашем веб-сайте, вы можете запуститьLighthouseПроверить.
Согласно Google, оптимальное дерево DOM:
- Менее 1500 узлов
- глубина менее 32 уровней
- Родительский узел имеет менее 60 дочерних узлов
Быстрый просмотр фида Dev.to показывает, что размер его дерева DOM довольно хорош:
- Всего 941 узел
- Максимальная глубина 14
- Максимальное количество дочерних элементов — 49.
неплохо!
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из ИнтернетаНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.