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

Несколько дней назад в группе серверной части узла состоялось обсуждение производительности масштабируемых приложений в Node.js. Никто не сомневался в скорости узла, но архитектура приложения и выполнение кода одинаково важны. Хорошо спроектированная система и чистый код играют жизненно важную роль в выполнении приложения. Поэтому я решил написать что-нибудь из области кода, чтобы сократить время выполнения кода.

Циклы - это важный элемент кода, который требуется разработчикам во многих ситуациях. Я решил подробно обсудить производительность выполнения циклов со статистикой, чтобы определить эффективность цикла в различных условиях и средах.

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

Мы будем тестировать производительность циклов на одномерных и зубчатых массивах (массив массивов), каждый цикл будет выполняться несколько раз, чтобы увидеть среднее время, затраченное на разные среды, машины и операционные системы. Давайте дадим шанс каждому.

Одномерный массив (10 миллионов итераций):

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

// populating single dimensional array
const arrData = [];
for (let i = 0; i < 10000000; i++) {
    arrData.push(i);
}
// different loops used in code
// classical for
for (let p = 0; p < arrData.length; p++) {}
// classical for with length
for (let p = 0, pLen = arrData.length; p < pLen; p++) {}
// reverse for
for (let pLen = arrData.length, p = pLen - 1; p >= 0; p--) {}
// reverse while
while (length--) {}
// es6
for (const pVal of arrData) {}
// for each
arrData.forEach((pVal) => {});
// lodash for each
__.forEach(arrData, (value) => {});

Следующие числа были сгенерированы в миллисекундах в разных средах:

Статистика довольно интересна на другой платформе, но классические циклы показали лучшую производительность с данными по сравнению с Foreach и ES6. Картинка стоит тысячи слов, поэтому лучше оценивать числа на графике:

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

Двумерный массив (100 миллионов итераций):

Набор из 10 000 элементов, каждый из которых дополнительно содержит 10 000 дочерних элементов, поэтому для посещения каждого элемента в цикле требуется 10 000 * 10 000 = 100 000 000 (100 миллионов итераций). Ниже приведен пример кода для заполнения структуры и синтаксиса различных циклов, используемых при тестировании:

// populating two dimensional array
const arrData = [];
// preparing sample data
for (let p = 0; p < 10000; p++) {
    arrData[p] = [];
    for (let c = 0; c < 10000; c++) {
        arrData[p].push(c);
    }
}
// different loops used in code
// classical for
for (let p = 0; p < arrData.length; p++) {
    for (let c = 0; c < arrData[p].length; c++) {}
}
// classical for with length defined
for (let p = 0, pLen = arrData.length; p < pLen; p++) {
    for (let c = 0, cLen = arrData[p].length; c < cLen; c++) {}
}
// reverse for
for (let pLen = arrData.length, p = pLen - 1; p >= 0; p--) {
    for (let cLen = arrData[p].length, c = cLen - 1; c >= 0; c--) {}
}
// reverse while
while (pLength--) {
  while (cLength--) {}
}
// es6
for (const pVal of arrData) {
    for (const cVal of pVal) {}
}
// for each
arrData.forEach((pVal) => {
    pVal.forEach((cVal) => {});
});
// lodash for each
__.forEach(arrData, (pValue) => {
    __.forEach(pValue, (cValue) => {});
});

Следующие числа были сгенерированы в миллисекундах:

Сравнение показывает, что классические ванильные петли снова работают лучше. Вышеупомянутые результаты были довольно неожиданными для неровного массива, поскольку он обратный, в то время как с вложенной структурой он ведет себя немного медленнее по сравнению с одномерным массивом, поэтому рекомендуется оценивать структуру в соответствии с ситуацией. Давайте сравним петли на гистограмме:

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

Логическая причина медлительности:

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

Механизмы языков высокого уровня реализуются с различными компромиссами, которые зависят от множества факторов, таких как производительность, скорость, простота обслуживания и т. Д., Которые играют жизненно важную роль в выполнении кода. У Stack Overflow есть очень приятный пост, относящийся к этой теме, который еще больше оправдывает причину медлительности.

Окончательные выводы (также из нескольких статей):

  1. Классические циклы намного лучше по производительности, но если массив имеет ограниченные данные и требуются простые манипуляции, то альтернативный вариант неплохо подходит для целей сопровождения кода.
  2. В зависимости от обстоятельств петли могут работать по-разному, поэтому соблюдайте их с умом в соответствии с требованиями.
  3. Присвоение длины массива другой переменной в операторе массива приводит к незначительной разнице в Node.js.
  4. Классические циклы не только лучше по производительности, но и позволяют избежать ошибок, которые возможны из-за гибкости javascript. У Stack Overflow есть очень хороший ответ, когда следует избегать, а когда использовать for… in loop.
  5. Всегда наблюдайте за производительностью кода в разных средах. Нет ничего идеального, кроме Creator, всегда есть место для импровизации.

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

Ссылки:







Производительность цикла JavaScript - почему уменьшать итератор в сторону 0 быстрее, чем увеличивать
В своей книге« Даже более быстрые веб-сайты
Стив Саундерс пишет, что простой способ повысить производительность цикл заключается в… stackoverflow.com »