Падение производительсности при операциях с arguments
Disclaimer: Я уверен, что это баян. Но быстрый поиск не нашел на Хабре аналогичной статьи. Если есть, пишете в комментах, удалю.Как-то раз, разглядывая CPU Profile, собранный в Chrome DevTools для нашего приложения, я заметил предупреждения на некоторых функциях о том, что они не были оптимизированы с причиной «Not optimized: Bad value context for arguments value».
Сама по себе проблема отключения оптимизации в Chrome богатая, но данная конкретная причина показалась странной. Гугление привело к этому посту, где автор утверждал, что предупреждение «Bad value context for arguments value» вызвано «неправильной работой с переменной arguments».
Я попытался разобраться в чем именно «неправильная работа» заключается, и насколько большое влияние это оказывает.Для этого создал тест: http://jsperf.com/optimizing-arguments.
Этот тесткейз состоит из нескольких реализаций функции `append` (типа той, что в Underscore). Каждая реализация копирует `arguments` в массив. Первая реализация для этого использует классический подход с `Array.slice`. Вторая реализация («copy (Array allocated)») копирует `arguments` в цикле, непосредственно обращаясь по индексу к элементам. Тест «copy (Array unallocated)» вариант теста «copy (Array allocated)», только использует литерал [] вместо создания экземпляра `Array`. Последний тест «copy via helper function» копирует `arguments` с помощью вспомогательной функции.
И вот результаты:
Что мы видим. Трехкратное падение производительности при использовании Array.slice. Но не только. Тест с вспомогательной функцией демонстрирует такие же результаты. Т.е. похоже на то, что любая передача `arguments` куда-то вовне функции приводит к падению производительности. А `Array.slice` это просто частный случай общей проблемы.
Что же получается. Раз мы не можем передать `arguments` куда бы то ни было, то мы вынуждены писать тупой код копирования `arguments` с помощью for снова и снова. Жуть. Но есть хорошие новости. TypeScript спешит на помощь. В TypeScript есть т.н. rest parameters. Определение функции с rest-параметрами означает, что javascript код будет использовать `arguments`. Но делает он это правильно.
Следующий TS-код:
export function extend (obj, …sources) { sources.forEach (function (source) { forEach (source, function (v, name) { obj[name] = v; }); }); return obj; } Cгенерирует: function extend (obj) { var sources = []; for (var _i = 1; _i < arguments.length; _i++) { sources[_i - 1] = arguments[_i]; } sources.forEach(function (source) { forEach(source, function (v, name) { obj[name] = v; }); }); return obj; } Отлично! То, что надо. Еще один маленький повод перейти на TypeScript.
P.S. Вот здесь полезный сборник причин отключения оптимизации в Chrome: https://github.com/GoogleChrome/devtools-docs/issues/53