Производительность ...spread оператора
В React распространенным методом изменения свойств объектов является применение spread оператора. Его синтаксис подкупает своей простой и понятностью.
Тем не менее, нужно понимать, что, в некоторых случаях, использование spread оператора может заметно сказаться на производительности вашего приложения. Разберем такой пример: мы получаем на входе массив объектов и нам необходимо создать из него один объект вида ключ-значение.
Если запустить код, то все сработает очень быстро. Но потенциальная проблема здесь кроется в том, что при каждом цикле reduce происходит полное копирование всего объекта.
Добавим код для простой оценки времени выполнения функции и увеличим размер массива:
У меня вышли следующие цифры:
- при длине массива 5, время выполнения составила 0.05 мс
- 1000 -> 115 мс
- 5000 -> 2.4 секунды!
Скорее всего, что подобное время выполнения не допустимо ни для frontend ни для backend. Для того, чтобы ускорить работу кода, нам нужно будет отказаться от принципов иммутабельности и просто вносить мутации в уже имеющийся объект:
Данная функция обработает массив из 5000 объектов уже за 2.5 мс. То есть, в 1000 раз быстрее!
Чуть медленнее, но также довольно быстро, с данной задачей справится метод _.set от популярной библиотеки lodash:
Время выполнения составило 4.6 мс. Что также довольно быстро.
Рассмотрим другие варианты решения данной задачи, если мы хотим продолжать придерживаться принципа иммутабельности.
Первым делом, я попробовал использовать ramda:
Время выполнения составило 1.4 секунды. Что все еще медленно, но все же быстрее, чем работа spread оператора.
Следующая популярная библиотека - immutable.js. Данная библиотека работает со своими структурами данных. Поэтому здесь мы еще дальше отойдем от "чистоты" эксперимента, и будем составлять не объект, а Map от immutable.js.
Результат - 13 мс. Что очень близко к нашему лучшему результату. Узким местом Immutable.js считается перевод полученных данных в обычный объект. Но, в данном случае, на общей производительности это сказалось очень мало:
Здесь мы получим почта такой же результат. Иммутабельные данные очень быстрые!
Последним вариантом будет Immer. Сначала используем прямолинейный подход:
...и получим катастрофичные 19 секунд! Но если обернуть в producе не каждый шаг, а всю функцию, то можно будет добиться значительного улучшения:
результат - 8.5 мс. Это все еще медленнее, чем funcMutate или funcLodash, но довольно близко.
Выводы?
Вряд ли можно вынести какие-либо практические выводы из данного мини-эксперимента. Да, spread оператор работает довольно медленно и, в некоторых случаях, являться узким местом в работе вашего приложения, когда приходится иметь дело с большими объектами или массивами.
Значительного увеличения производительности можно добиться отказавшись от принципа иммутабельности данных. Если этот вариант вам не подходит, то хорошим решением могут быть Immer или Immutable.