Циклы играют жизненно важную роль в производительности приложения. Наиболее часто используемые циклы, влияние выполнения измеряется на огромной выборке данных.
Несколько дней назад в группе серверной части узла состоялось обсуждение производительности масштабируемых приложений в 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 есть очень приятный пост, относящийся к этой теме, который еще больше оправдывает причину медлительности.
Окончательные выводы (также из нескольких статей):
- Классические циклы намного лучше по производительности, но если массив имеет ограниченные данные и требуются простые манипуляции, то альтернативный вариант неплохо подходит для целей сопровождения кода.
- В зависимости от обстоятельств петли могут работать по-разному, поэтому соблюдайте их с умом в соответствии с требованиями.
- Присвоение длины массива другой переменной в операторе массива приводит к незначительной разнице в Node.js.
- Классические циклы не только лучше по производительности, но и позволяют избежать ошибок, которые возможны из-за гибкости javascript. У Stack Overflow есть очень хороший ответ, когда следует избегать, а когда использовать for… in loop.
- Всегда наблюдайте за производительностью кода в разных средах. Нет ничего идеального, кроме Creator, всегда есть место для импровизации.
Ударь меня за любую находку, чтобы поделиться с гиками. Дальнейший код и структурные области будут исследованы в следующих статьях, оставайтесь с нами.
Ссылки:
Производительность цикла JavaScript - почему уменьшать итератор в сторону 0 быстрее, чем увеличивать
В своей книге« Даже более быстрые веб-сайты Стив Саундерс пишет, что простой способ повысить производительность цикл заключается в… stackoverflow.com »