...

понедельник, 25 мая 2015 г.

Падение производительсности при операциях с arguments

Disclaimer: Я уверен, что это баян. Но быстрый поиск не нашел на Хабре аналогичной статьи. Если есть, пишете в комментах, удалю.

Как-то раз, разглядывая CPU Profile, собранный в Chrome DevTools для нашего приложения, я заметил предупреждения на некоторых функциях о том, что они не были оптимизированы с причиной «Not optimized: Bad value context for arguments value».

Сама по себе проблема отключения оптимизации в Chrome богатая, но данная конкретная причина показалась странной. Гугление привело к этому посту, где автор утверждал, что предупреждение «Bad value context for arguments value» вызвано «неправильной работой с переменной arguments».

Я попытался разобраться в чем именно «неправильная работа» заключается, и насколько большое влияние это оказывает.

Для этого создал тест: http://ift.tt/1FM21Wg.

Этот тесткейз состоит из нескольких реализаций функции `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;
}


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: http://ift.tt/1lj2bcj

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий