Падение производительсности при операциях с 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` с помощью вспомогательной функции.

И вот результаты:
image

Что мы видим. Трехкратное падение производительности при использовании 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; } 

сгенерирует:

    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

ссылка на оригинал статьи http://habrahabr.ru/post/258741/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *