Анализ производительности и предвзятости циклов и методов JavaScript при работе с различными наборами данных.

Введение в циклы и методы

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

Все начинают с вашего основного цикла for. Как только новые разработчики узнают об этом, их умы взрываются, и жизнь становится проще. Этот умопомрачительный опыт происходит снова и снова по мере появления новых методов. Что интересно, как только вводятся новые цикл и методы (while, forEach, map, filter и т. Д.), Основной цикл for надолго остается в тени. Это происходит где-то с первой по третий месяц и далее. Разработчику требуется много времени или определенный опыт работы с данными, чтобы вернуться и снова рассмотреть базовый цикл for для достижения своих целей.

По этой причине мы собираемся увидеть, есть ли какое-либо оправдание для использования только таких методов, как forEach и map, или есть ли какие-либо преимущества в том, чтобы придерживаться испытанного и истинного цикла for.

Типы данных

Мы собираемся рассмотреть каждый из этих циклов, чтобы выявить их преимущества и недостатки по сравнению с примитивными и непримитивными типами данных. Если вам нужно напомнить об этих типах данных, вот список, с которым вы обычно работаете.

Примитивы

  1. Цифры
  2. Струны
  3. Логические
  4. "Неопределенный"
  5. "Нулевой"

Непримитивный

  1. "Объекты"
  2. Массивы
  3. Функции

Поиск значения в массиве

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

  1. "за"
  2. За… из
  3. "для каждого"
  4. "пока"
  5. "делать пока"
  6. "найти"
  7. FindIndex
  8. "индекс чего-либо"
  9. LastIndexOf
  10. "включает"
  11. "карта"
  12. "фильтр"
  13. "уменьшать"

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

Примеры примитивных массивов:

let namesArray = ['Abe', 'Beth', 'Cody', 'Daniel'];
let textArray = ['Dog', 'Cat', 'Horse', 'Cow'];
let numbersArray = [1, 2, 3, 4];

Стартовый код

// Objectives:
// 1. Find the value 7
// 2. Find the index of 7
const OBJECTIVE_NUMBER = 7;
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let foundValue;
let foundIndex = -1;

Приведем пример кода, который мы будем использовать для сравнительного анализа. Чтобы увидеть полный список примеров циклов и методов, нажмите здесь!

Пример "цикла"

// Using array and variables from base code block above…
for (let index = 0; index < arr.length; index++) {
  const value = arr[index];
  if(value === OBJECTIVE_NUMBER) {
    foundValue = value;
    foundIndex = index;
    break;
  }
};
console.log(foundValue); // expected output: 7;
console.log(foundIndex); // expected output: 6;

Сравнительный анализ кода

Теперь, когда у нас есть базовое представление о каждом из циклов и возможностях, которые они привносят в таблицу, мы можем увидеть, как они работают с небольшими и большими наборами данных. Мы собираемся включить map, filter и reduce, даже если они используются как анти-шаблон, чтобы продемонстрировать производительность по всем направлениям. Мы также протестируем наши итерации на поиск значения около начала и конца массива для каждого цикла и метода. Мы также протестируем их в разных браузерах, чтобы измерить производительность движков JavaScript каждого браузера (Chakra, V8 и SpiderMonkey), которые повторяют и оптимизируют наши циклы в фоновом режиме.

Массивы, которые мы будем использовать:

  1. Массив 1: 100 примитивных значений;
  2. Массив 2: 1000 примитивных значений;
  3. Массив 3: 10 000 примитивных значений;

Примечание. В части 2 мы рассмотрим те же циклы, но в сравнении с непримитивами (объектами, массивами, функциями) и измерим производительность по ним.

Окончательные результаты

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

Примечание. На графиках представлен каждый цикл или метод, а также количество операций в секунду (операций в секунду), выполняемых в течение заданного периода времени.

Хром

Край

Fire Fox

Разбивка результатов

Посмотрев на графики, можно сделать несколько общих выводов:

  1. По мере увеличения наборов данных map, reduce и filter работают хуже всего, если используются против их предполагаемого назначения или определения.
  2. В отличие от небольших массивов движок Firefox (SpiderMonkey) оптимизирован для всех методов, чтобы перебирать массивы и находить значения как в начале, так и в конце указанных массивов.
  3. lastIndexOf работает нормально. Хуже при поиске в начале массива и лучше всего при поиске конечных значений. Поскольку это ожидается, мы удалим этот метод при сравнении общей производительности.

Массивы малых размеров

Давайте начнем с небольших массивов для некоторых общих выводов.

  1. Edge: forEach, map и reduce работают лучше всего.
  2. Chrome: forEach, map и reduce работают лучше всего.
  3. Firefox: все методы, кроме map, filter и reduce, работают хорошо, но не намного.
  4. Общий результат: forEach

Массивы среднего размера

Далее мы замечаем, что с массивами среднего размера, особенно при поиске значений в конце массива, производительность начинает сильно меняться по всем циклам и методам.

  1. Edge: indexOf и includes работают лучше, за ними следует while, do… while, for и for… of.
  2. Chrome: indexOf и includes принимают пирог за производительность, за которыми следуют for, while и do… while.
  3. Firefox: здесь зафиксирована более высокая производительность, чем в Edge и Chrome. for, while, indexOf и includes - все высокопроизводительные.
  4. Показатели в целом: indexOf and while, поскольку мы обычно смотрим от корки до корки в поисках нашей ценности.

Массивы большого размера

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

  1. Edge: for, while и indexOf работают лучше всего. Большинство других циклов и методов работают плохо.
  2. Chrome: пока indexOf и includes остаются наверху, хотя мы снова видим, что большинство других методов не работают на том же уровне.
  3. Firefox: for, while и indexOf снова являются главными претендентами с таким же падением, наблюдаемым с большинством оставшихся циклов и методов.
  4. Показатели общего успеха: пока и пока.

Вывод

Надеюсь, что в результате просмотра данных мы все сможем принимать более правильные решения о методах, которые мы хотим использовать для различных наборов данных. Если мы работаем с данными, которые со временем могут расти, и нам приходится перебирать все эти данные, может быть целесообразно вернуться к надежному циклу for, который всегда был рядом с нами. Тем более, что вы можете воспользоваться его наследуемой способностью останавливаться до цикла с помощью break и return, как только вы закончите намеченное действие. Хотя это может выглядеть не очень красиво, оно всегда будет удобно.

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

Обновление: Часть 2 доступна здесь.