3 ошибки производительности JavaScript, которые вы должны прекратить совершать

Tags: JavaScript

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

Эта история начинается несколько лет назад, в наивные дни ES5 ...

Момент выпуска ES5 запомнился многим, когда новые классные функции массива были введены в наш дорогой JavaScript.  Среди них были для Each, reduce, map, filter - они заставляли нас чувствовать, что язык растет, становится более функциональным, писать код становится более увлекательным и плавным, а результат читается легче и становится более понятным.

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

В настоящее время Node.js, использующий последний ECMAScript над V8, пытается быть признанным частью основных языков разработки на стороне сервера лиги, и поэтому он должен оказаться достойным производительности. Да, есть очень много параметров, которые нужно учитывать, и да, нет идеального языка, который превосходит всех. Но, действительно ли написание кода JavaScript с использованием готовых функций, подобные упомянутой выше функции массива, помогает производительности вашего приложения? Или это все же наносит ущерб?

Кроме того, javascript на стороне клиента утверждает, что он является разумным решением для большего, чем просто представления \ просмотра, поскольку компьютеры конечных пользователей становятся сильнее, а сети быстрее. Но можем ли мы полагаться на это, когда наше приложение требует высокой производительности и может быть очень большим и сложным?

Чтобы проверить это, мы попытались сравнить несколько сценариев и подробно изучили их, чтобы понять результаты, которые были получены. Мы выполнили следующие тесты на Node.js v10.11.0 и в браузере Chrome, и на macOS.

1. Зацикливание по массиву

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

Мы сравнивали суммирование случайных 10 тыс. элементов, используя for, for-of, while, forEach и уменьшая. Запуск тестов 10 000 раз дал следующие результаты:

For Loop, average loop time: ~10 microseconds

For-Of, average loop time: ~110 microseconds

ForEach, average loop time: ~77 microseconds

While, average loop time: ~11 microseconds

Reduce, average loop time: ~113 microseconds

В процессе поиска решения, как суммировать массив, сокращение было наилучшим решением, но оно является самым медленным. Наш переход к forEach  был не намного лучше. Даже новейший for-of (ES6) обеспечивает низкую производительность. Оказывается, старый добрый цикл for (а также while) обеспечивает производительность в 10 раз лучше!

Как может новейшее и рекомендуемое решение сделать JavaScript намного медленнее? Причина этой боли объясняется двумя основными причинами: сокращение и forEach требует выполнения функции обратного вызова, которая вызывается рекурсивно и раздувает стек, а также дополнительную операцию и проверку, которые выполняются над исполняемым кодом.

2. Дублирование массива

Хотя это звучит как менее интересный сценарий, это столп неизменяемых функций, который не изменяет ввод при генерации вывода.

Результаты тестирования производительности здесь снова показывают одну и ту же интересную тенденцию - при дублировании 10 тыс. массивов из 10 тыс. случайных предметов оказалось быстрее использовать старые привычные решения.

Опять же самая модная операция распространения ES6 `[... arr]` и Array из `Array.from (arr)` плюс карта ES5 `arr.map (x => x)` уступают проверенному срезу `arr.slice () `и concatenate` [] .concat (arr) `.

Duplicate using Slice, average: ~367 microseconds

Duplicate using Map, average: ~469 microseconds

Duplicate using Spread, average: ~512 microseconds

Duplicate using Conct, average: ~366 microseconds

Duplicate using Array From, average: ~1,436 microseconds

Duplicate manually, average: ~412 microseconds

3. Итерация объектов

Другим частым сценарием является итерация над объектами, это в основном необходимо, когда мы пытаемся пересечь JSON и объекты и не ищем определенного значения ключа. Опять же существуют ветеранские решения, такие как for-in `for (let key in obj)` или более поздние `Object.keys (obj)` (представленные в es6) и `Object.entries (obj)` (из ES8) который возвращает оба ключа и значение.

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

Object iterate For-In, average: ~240 microseconds

Object iterate Keys For Each, average: ~294 microseconds

Object iterate Entries For-Of, average: ~535 microseconds

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

Подведем итоги

Вывод ясен: если для вашего приложения важна высокая производительность, или если вашим серверам требуется некоторая загрузка - самые крутые, более читаемые и более чистые варианты будут сильно влиять на производительность вашего приложения, которая может стать до 10 раз медленнее!

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



No Comments

Add a Comment