Как быстро писать, памяти-эффективный JavaScript

Двигатели JavaScript, такие как V8 от Google (Chrome, Node), специально разработаны для быстрого выполнения больших приложений JavaScript. При разработке, если вы заботитесь об использовании памяти и производительности, вы должны быть осведомлены о некоторых из того, что происходит в браузере пользователя JavaScript двигателя за кулисами.

Будь то V8, SpiderMonkey (Firefox), Каракан (Опера), Чакра (IE) или что-то еще, это может помочь вам лучше оптимизировать приложения. Это не означает, что нужно оптимизировать для одного браузера или двигателя. Никогда так не делай.

Однако вы должны задать себе такие вопросы, как:

  • Есть ли что-нибудь, что я мог бы сделать более эффективно в моем коде?
  • Какие (общие) оптимизации делают популярные двигатели JavaScript?
  • Для чего двигатель не в состоянии оптимизировать, и способен ли сборщик мусора очистить то, что я ожидал?

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

Дальнейшее чтение на SmashingMag:

Итак, как JavaScript работает в V8?

Хотя можно разрабатывать крупномасштабные приложения без тщательного понимания двигателей JavaScript, любой автовладелец скажет вам, что они смотрели под капотом по крайней мере один раз. Как Chrome мой браузер выбора, я собираюсь поговорить немного о его JavaScript двигателя. V8 состоит из нескольких основных частей.

  • Базовый компилятор,который разбирает ваш JavaScript и генерирует родной код машины до его выполнения, а не выполняет байтокод или просто интерпретирует его. Этот код изначально не сильно оптимизирован.
  • V8 представляет ваши объекты в модели объекта. Объекты представлены как ассоциативные массивы в JavaScript, но в V8 они представлены скрытыми классами,которые являются внутренней системой типа для оптимизированного поиска.
  • Профайлер времени выполнения отслеживает работу системы и определяет «горячие» функции (т.е. код, который в конечном итоге тратит длительное время на работу).
  • Оптимизация компилятора перекомпилирует и оптимизирует «горячий» код, идентифицированный профилировщиком времени выполнения, и выполняет оптимизацию, такую как выравнивание (т.е. замена сайта вызова функции телом вызывающей абонента).
  • V8 поддерживает деоптимизацию,что означает, что оптимизирующий компилятор может выручить код, созданный, если он обнаружит, что некоторые предположения, сделанные им в отношении оптимизированного кода, были слишком оптимистичными.
  • Он имеет сборщик мусора. Понимание того, как это работает, может быть столь же важным, как и оптимизированный JavaScript.

Сбор мусора

Сбор мусора является одной из форм управления памятью. Это место, где у нас есть понятие коллектора, который пытается восстановить память, занятую объектами, которые больше не используются. На языке, собранном мусором, таком как JavaScript, объекты, на которые по-прежнему ссылаются в приложении, не очищаются.

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

javascript performance
Сбор мусора пытается восстановить память. Источник изображения: Валттери Мяки.

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

Де-Ссылки Заблуждения

В довольно много дискуссий в Интернете о восстановлении памяти в JavaScript, delete ключевое слово воспитывается, как, хотя он должен был быть использован для простого удаления ключей с карты, некоторые разработчики думают, что вы можете заставить де-ссылки, используя его. Избегайте использования, delete если вы можете. В приведенном ниже примере, delete o.x делает гораздо больше вреда, чем пользы за кулисами, как это меняет o ‘S скрытый класс и делает его общим медленным объектом.

var o = { x: 1 };
delete o.x; // true
o.x; // undefined

Тем не менее, вы почти наверняка найдете ссылки delete на во многих популярных библиотеках JavaScript — у него есть цель в языке. Основной вынос здесь, чтобы избежать изменения структуры горячих объектов во время выполнения. Двигатели JavaScript могут обнаруживать такие «горячие» объекты и пытаться оптимизировать их. Это проще, если структура объекта не сильно меняется в течение всего срока службы и delete может вызвать такие изменения.

Есть также заблуждения о том, как null работает. Установка ссылки на объект null не «обнуляет» объект. Он устанавливает ссылку объекта на null . Использование o.x = null лучше, чем delete использование, но это, вероятно, даже не нужно.

var o = { x: 1 };
o = null;
o; // null
o.x // TypeError

Если эта ссылка была последней ссылкой на объект, объект имеет право на сбор мусора. Если ссылка не была последней ссылкой на объект, объект доступен и не будет собираться мусором.

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

var myGlobalNamespace = {};

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

Правила большого пальца

Чтобы дать сборщику мусора возможность собрать как можно больше предметов как можно раньше, не держитесь за объекты,которые вам больше не нужны. Это в основном происходит автоматически; вот несколько вещей, чтобы иметь в виду.

  • Как упоминалось ранее, лучшей альтернативой ручной де-ссылки является использование переменных с соответствующей областью. Т.е. вместо глобальной переменной, которая аннулируется, просто используйте переменную функционально-локальной, которая выходит за рамки, когда она больше не нужна. Это означает, что более чистый код с меньшими для беспокойства.
  • Убедитесь, что вы необязательные слушатели событий, где они больше не требуются, особенно когда объекты DOM они обязаны быть удалены
  • Если вы используете кэш данных локально, убедитесь, что очистить этот кэш или использовать механизм старения, чтобы избежать больших кусков данных, которые вы вряд ли повторно использовать

Функции

Далее рассмотрим функции. Как мы уже говорили, сбор мусора работает путем рекультивации блоков памяти (объектов), которые больше не доступны. Чтобы лучше проиллюстрировать это, вот несколько примеров.

function foo() {
    var bar = new LargeObject();
    bar.someCall();
}

При foo возврате объект, на который bar указывает, автоматически доступен для сбора мусора, потому что не осталось ничего, что имеет ссылку на него.

Сравните это с:

function foo() {
    var bar = new LargeObject();
    bar.someCall();
    return bar;
}

// somewhere else
var b = foo();

Теперь у нас есть ссылка на объект, который выживает вызова и сохраняется до тех пор, пока абонент назначает что-то другое b (или b выходит из сферы действия).

Замыкания

Когда вы видите функцию, которая возвращает внутреннюю функцию, эта внутренняя функция будет иметь доступ к внешней области даже после выполнения внешней функции. Это в основном замыкание — выражение, которое может работать с переменными, установленными в определенном контексте. Например:

function sum (x) {
    function sumIt(y) {
        return x + y;
    };
    return sumIt;
}

// Usage
var sumA = sum(4);
var sumB = sumA(3);
console.log(sumB); // Returns 7

Объект функции, созданный в контексте выполнения sum вызова, не может быть собран мусором, так как на него ссылается глобальная переменная и все еще очень доступна. Он все еще может быть выполнен через sumA(n) .

Давайте посмотрим на другой пример. Здесь, мы можем получить largeStr доступ?

var a = function () {
    var largeStr = new Array(1000000).join('x');
    return function () {
        return largeStr;
    };
}();

Да, мы можем, через a() , так что это не собрано. Как насчет этого?

var a = function () {
    var smallStr = 'x';
    var largeStr = new Array(1000000).join('x');
    return function (n) {
        return smallStr;
    };
}();

Мы не можем получить к нему доступ больше, и это кандидат на сбор мусора.

Таймеры

Один из худших мест для утечки находится в петле, или в setTimeout() / , но это довольно setInterval() часто.

Рассмотрим следующий пример.

var myObj = {
    callMeMaybe: function () {
        var myRef = this;
        var val = setTimeout(function () {
            console.log('Time is running out!');
            myRef.callMeMaybe();
        }, 1000);
    }
};

Если мы затем запустить:

myObj.callMeMaybe();

чтобы начать таймер, мы можем видеть каждую секунду «Время уходит!» Если мы затем запустить:

myObj = null;

Таймер все равно будет гореть. myObjне будет мусора собраны, как закрытие прошло setTimeout должно быть сохранено в живых, чтобы быть выполнены. В свою очередь, он содержит ссылки myObj на, как он захватывает myRef . Это было бы то же самое, если бы мы передали закрытие любой другой функции, сохраняя ссылки на него.

Также стоит иметь в виду, что ссылки внутри setTimeout / setInterval вызова, такие как функции, необходимо будет выполнить и завершить, прежде чем они могут быть собраны мусора.

Будьте в курсе ловушки производительности

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

Speed trap.
Делать слишком много может быть так же вредно, как ничего не делать. Источник изображения: Тим Ширман-Чейз.

Допустим, мы хотим создать модуль, который:

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

Есть несколько различных факторов этой проблемы, хотя это довольно просто решить. Как мы храним данные? Как эффективно нарисовать таблицу и придать его ДОМ? Как мы оптимально обращаемся с событиями на этом столе?

Первый (наивный) взгляд на эту проблему может заключаться в хранении каждой части доступных данных в объекте, который мы группирует в массив. Можно было бы использовать j’ery для итерации данных и рисовать таблицу, а затем придать его DOM. Наконец, можно использовать привязку событий для добавления желаемого нам поведения кликов.

Примечание: Это не то, что вы должны делать

var moduleA = function () {

    return {

        data: dataArrayObject,

        init: function () {
            this.addTable();
            this.addEvents();
        },

        addTable: function () {

            for (var i = 0; i < rows; i++) {
                $tr = $('<tr></tr>');
                for (var j = 0; j < this.data.length; j++) {
                    $tr.append('<td>' + this.data[j]['id'] + '</td>');
                }
                $tr.appendTo($tbody);
            }

        },
        addEvents: function () {
            $('table td').on('click', function () {
                $(this).toggleClass('active');
            });
        }

    };
}();

Просто, но он получает работу.

Однако в этом случае единственными данными, которые мы итерируем, являются итоговые иудеи, числовое свойство, которое может быть более просто представлено в стандартном массиве. Интересно, что непосредственное использование DocumentFragment и родные методы DOM являются более оптимальными, чем использование j’ery (таким образом) для нашего генерации таблицы, и, конечно, делегирование событий, как правило, более performant, чем td связывание каждого по отдельности.

Обратите внимание, что j’ery использует DocumentFragment внутренне за кулисами, но в нашем примере, код вызывает append() в цикле, и каждый из этих вызовов имеет мало знаний о другом, поэтому он не может быть в состоянии оптимизировать для этого примера. Мы надеемся, что это не будет болеутоляще, но не забудьте снабдить свой собственный код, чтобы быть уверенным.

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

var moduleD = function () {

    return {

        data: dataArray,

        init: function () {
            this.addTable();
            this.addEvents();
        },
        addTable: function () {
            var td, tr;
            var frag = document.createDocumentFragment();
            var frag2 = document.createDocumentFragment();

            for (var i = 0; i < rows; i++) {
                tr = document.createElement('tr');
                for (var j = 0; j < this.data.length; j++) {
                    td = document.createElement('td');
                    td.appendChild(document.createTextNode(this.data[j]));

                    frag2.appendChild(td);
                }
                tr.appendChild(frag2);
                frag.appendChild(tr);
            }
            tbody.appendChild(frag);
        },
        addEvents: function () {
            $('table').on('click', 'td', function () {
                $(this).toggleClass('active');
            });
        }

    };

}();

Затем мы могли бы искать другие способы повышения производительности. Возможно, вы где-то прочитали, что использование шаблона prototypal является более оптимальным, чем шаблон модуля (мы подтвердили, что это не было раньше), или слышали, что использование рамок шаблона JavaScript сильно оптимизированы. Иногда они есть, но использовать их, потому что они делают для читаемого кода. Кроме того, прекомпилс!. Давайте проверим и выясним, насколько это верно на практике.

moduleG = function () {};

moduleG.prototype.data = dataArray;
moduleG.prototype.init = function () {
    this.addTable();
    this.addEvents();
};
moduleG.prototype.addTable = function () {
    var template = _.template($('#template').text());
    var html = template({'data' : this.data});
    $tbody.append(html);
};
moduleG.prototype.addEvents = function () {
   $('table').on('click', 'td', function () {
       $(this).toggleClass('active');
   });
};

var modG = new moduleG();

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

Более сложные проблемы включают эффективное рисование изображений с помощью холста и манипулирование пиксельными данными с набранными массивами или без них

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

V8 Оптимизация Советы

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

  • Некоторые закономерности вызовут V8, чтобы выручить от оптимизации. Попытка поймать, например, вызовет такой кризиса. Для получения дополнительной информации о том, какие функции могут и не могут быть оптимизированы, вы можете использовать --trace-opt file.js с d8 оболочки утилита, которая поставляется с V8.
  • Если вы заботитесь о скорости, старайтесь очень сильно держать свои функции мономорфными, т.е. убедитесь, что переменные (включая свойства, массивы и параметры функции) содержат только объекты с тем же скрытым классом. Например, не делайте этого:
function add(x, y) {
   return x+y;
}

add(1, 2);
add('a','b');
add(my_custom_object, undefined);
  • Не загружайте из неиницеированных или удаленных элементов. Это не будет иметь значения в выходе, но это сделает вещи медленнее.
  • Не пишите огромные функции, так как их сложнее оптимизировать

Для получения дополнительных советов, смотреть Даниэль Клиффорд в Google I / O говорить Нарушение JavaScript ограничение скорости с V8, как она охватывает эти темы хорошо. Оптимизация для V8 — Серия также стоит прочитать.

Объекты Vs. Arrays: Что я должен использовать?

  • Если вы хотите сохранить кучу чисел или список объектов одного и того же типа, используйте массив.
  • Если вам семантически нужен объект с кучей свойств (разных типов), используйте объект с свойствами. Это довольно эффективно с точки зрения памяти, и это также довольно быстро.
  • Элементы, проиндексированные integer, независимо от того, хранятся ли они в массиве или объекте, гораздо быстрее итерируются, чем свойства объектов.
  • Свойства на объектах довольно сложны: они могут создаваться с помощью сеттеров, и с различной перечней и чтивостью. Элементы в массивах не могут быть настроены как сильно — они либо существуют, либо нет. На уровне двигателя это позволяет проводить дополнительную оптимизацию с точки зрения организации памяти, представляющей структуру. Это особенно полезно, когда массив содержит числа. Например, когда вам нужны векторы, не определяйте класс с свойствами x, y, z; использовать массив вместо.

На самом деле в JavaScript есть только одно существенное различие между объектами и массивами, и это магическое length свойство массивов. Если вы отслеживаете это свойство самостоятельно, объекты в V8 должны быть такими же быстрыми, как и массивы.

Советы при использовании объектов

  • Создавайте объекты с помощью функции конструктора. Это гарантирует, что все объекты, созданные с ним, имеют один и тот же скрытый класс и помогает избежать изменения этих классов.
  • Нет никаких ограничений на количество различных типов объектов, которые можно использовать в приложении, или на их сложность (в пределах разумного: длинные цепочки прототипов, как правило, болеют, а объекты с несколькими свойствами получают специальное представление, которое немного быстрее чем большие объекты). Для «горячих» объектов старайтесь держать цепочки прототипов короткими, а поле низкое.

Клонирование объекта
Клонирование объектов является общей проблемой для разработчиков приложений. Хотя можно учесть, насколько хорошо различные реализации работают с этим типом проблемы в V8, будьте очень осторожны при копировании чего-либо. Копирование больших вещей, как правило, медленно — не делайте этого. for..inпетли в JavaScript особенно плохо для этого, так как они имеют дьявольской спецификации и, вероятно, никогда не будет быстрым в любом двигателе для произвольных объектов.

Когда вам абсолютно необходимо скопировать объекты в критически важном для производительности пути кода (и вы не можете выйти из этой ситуации), используйте массив или пользовательский функцию «конструктор копирования», которая копирует каждое свойство явно. Это, вероятно, самый быстрый способ сделать это:

function clone(original) {
  this.foo = original.foo;
  this.bar = original.bar;
}
var copy = new clone(original);

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

Performance improvements
Улучшения производительности при использовании модуля или прототипических шаблонов.

Вот тест производительности прототипа по сравнению с модульным шаблоном

// Prototypal pattern
  Klass1 = function () {}
  Klass1.prototype.foo = function () {
      log('foo');
  }
  Klass1.prototype.bar = function () {
      log('bar');
  }

  // Module pattern
  Klass2 = function () {
      var foo = function () {
          log('foo');
      },
      bar = function () {
          log('bar');
      };

      return {
          foo: foo,
          bar: bar
      }
  }

  // Module pattern with cached functions
  var FooFunction = function () {
      log('foo');
  };
  var BarFunction = function () {
      log('bar');
  };

  Klass3 = function () {
      return {
          foo: FooFunction,
          bar: BarFunction
      }
  }

  // Iteration tests

  // Prototypal
  var i = 1000,
      objs = [];
  while (i--) {
      var o = new Klass1()
      objs.push(new Klass1());
      o.bar;
      o.foo;
  }

  // Module pattern
  var i = 1000,
      objs = [];
  while (i--) {
      var o = Klass2()
      objs.push(Klass2());
      o.bar;
      o.foo;
  }

  // Module pattern with cached functions
  var i = 1000,
      objs = [];
  while (i--) {
      var o = Klass3()
      objs.push(Klass3());
      o.bar;
      o.foo;
  }
// See the test for full details

Примечание: Если вам не нужен класс, избегайте проблем с его созданием. Вот пример того, как повысить производительность путем переосвечивания класса накладные расходы в целом.

Советы при использовании массивов

Далее давайте посмотрим на несколько советов для массивов. Как правило, не удаляйте элементы массива. Это сделает переход массива к более медленному внутреннему представлению. Когда набор ключей становится редким, V8 в конечном итоге переключить элементы в режим словаря, который еще медленнее.

Array Литературы
Массив буквально полезны, потому что они дают подсказку VM о размере и типе массива. Они, как правило, хороши для малых и средних массивов.

// Here V8 can see that you want a 4-element array containing numbers:
var a = [1, 2, 3, 4];

// Don't do this:
a = []; // Here V8 knows nothing about the array
for(var i = 1; i <= 4; i++) {
     a.push(i);
}

Хранение одиночных типов Vs. Смешанные типы
Это никогда не хорошая идея, чтобы смешать значения различных типов (например, номера, строки, неопределенные или правда / ложь) в том же массиве (т.е. var arr = [1, “1”, undefined, true, “true”] )

Проверка производительности выводов типа

Как мы можем видеть из результатов, массив ints является самым быстрым.

Редкие массивы против полных массивов
При использовании разреженнонастроенных массивов следует знать, что доступ к элементам в них происходит гораздо медленнее, чем во всех массивах. Это потому, что V8 не выделяет плоский резервный магазин для элементов, если только некоторые из них используются. Вместо этого он управляет ими в словаре, что экономит пространство, но стоит времени на доступ.

Тест разреженности массивов по сравнению с полными массивами.

Полный массив sum и sum все элементы на массиве без нулей были на самом деле самыми быстрыми. Независимо от того, содержит ли полный массив нулей или нет, не следует иметь значение.

Упакованные Vs. Холи Arrays
Избегайте «дыр» в массиве (созданных путем удаляния элементов или a[x] = foo с x > a.length ). Даже если только один элемент будет удален из «полного» массива, все будет гораздо медленнее.

Тест упакованных против дырявых массивов.

Предварительное выделение массивов Vs. Растущие по мере того, как вы идете
Не предварительно распределите большие массивы (т.е. больше, чем 64K элементы) к их максимальному размеру, а не расти, как вы идете. Прежде чем мы перейдем к тестам производительности для этого наконечника, имейте в виду, что это характерно только для некоторых двигателей JavaScript.

Test of empty literal versus pre-allocated arrays in various browsers.
Тест пустого буквального и заранее выделенного массива в различных браузерах.

Nitro (Safari) на самом деле относится к заранее выделенным массивам более благоприятно. Однако, в других двигателях (V8, SpiderMonkey), не предварительное выделение является более эффективным.

Тестирование заранее выделенных массивов.

// Empty array
var arr = [];
for (var i = 0; i < 1000000; i++) {
    arr[i] = i;
}

// Pre-allocated array
var arr = new Array(1000000);
for (var i = 0; i < 1000000; i++) {
    arr[i] = i;
}

Оптимизация приложения

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

An old phone on the screen of an iPad.
Источник изображения: Пер Улоф Форсберг.

Хотя понимание и повышение производительности приложения полезно, это также может быть трудно. Мы рекомендуем следующие шаги, чтобы исправить точки боли производительности:

  • Измерьте его: Найдите медленные пятна в приложении (45%)
  • Поймите его: Узнайте, в чем заключается реальная проблема (45%)
  • Исправь это! (10%)

Некоторые из инструментов и методов, рекомендованных ниже, могут помочь в этом процессе.

Бенчмаркинга

Есть много способов запуска тестовых на фрагментах JavaScript, чтобы проверить их производительность — общее предположение, что бенчмаркинг просто сравнивает две метки времени. Один из таких шаблонов был указан командой jsPerf, и, случается, используется в бенчмарке SunSpider‘s и Kraken’

var totalTime,
    start = new Date,
    iterations = 1000;
while (iterations--) {
  // Code snippet goes here
}
// totalTime → the number of milliseconds taken
// to execute the code snippet 1000 times
totalTime = new Date - start;

Здесь код, который будет протестирован, помещается в цикл и запускается определенное количество раз (например, шесть). После этого дата начала вычитается из даты окончания, чтобы найти время, заработавщее для выполнения операций в цикле.

Тем не менее, это упрощает, как бенчмаркинг должен быть сделан, особенно если вы хотите запустить тесты в нескольких браузерах и средах. Сбор мусора сам по себе может повлиять на ваши результаты. Даже если вы используете window.performance решение, как, вы все равно должны учитывать эти подводные камни.

Независимо от того, просто ли вы используете бенчмарки по частям кода, пишете набор тестов или кодируете библиотеку бенчмаркинга, в бенчмаркинге JavaScript есть намного больше, чем вы думаете. Для более подробного руководства по бенчмаркингу, я настоятельно рекомендую прочитать JavaScript Benchmarking Матиасом Байненсом и Джоном-Дэвидом Далтоном.

Профилирования

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

Profiles panel in Chrome Developer Tools.
Панель профилей в Chrome Developer Tools.

Профилирование начинается с получения базовой для текущей производительности кода, которая может быть обнаружена с помощью Хроники. Это подскажет нам, сколько времени занял наш код. Вкладка Профили затем дает нам лучшее представление о том, что происходит в нашем приложении. Профиль процессора JavaScript показывает нам, сколько времени процессора используется нашим кодом, профиль селектора CSS показывает нам, сколько времени тратится на обработку селекторов, а снимки кучи показывают, сколько памяти используется нашими объектами.

Используя эти инструменты, мы можем изолировать, настроить и перепрофилировать, чтобы оценить, являются ли изменения, которые мы вносим в конкретные функции или операции, повышение производительности.

The profile tab gives information about your code's performance.
Вкладка «Профиль» дает вам информацию о производительности кода.

Для хорошего введения в профилирование, читать JavaScript Профилирование с Chrome Developer Tools, Зак Гроссбарт.

Совет: В идеале необходимо убедиться, что профилирование не будет зависеть от установленных расширений или приложений, поэтому запустите Chrome с помощью –user-data-dir <empty_directory> флага. Большую часть времени, этот подход к тестированию оптимизации должно быть достаточно, но Есть моменты, когда вам нужно больше. Это где V8 флаги могут быть помогли.

Как избежать утечки памяти — три методы снимка для обнаружения

Внутренне в Google, Chrome Developer Tools в значительной степени используются такими группами, как Gmail, чтобы помочь нам обнаружить и сквош утечки памяти.

Memory statistics in Chrome Developer Tools.
Статистика памяти в Chrome Developer Tools.

Некоторые статистические данные о памяти, которые волнуют наши группы, включают частное использование памяти, размер кучи JavaScript, количество узлов DOM, очистку хранилища, количество слушателей событий и то, что происходит со сбором мусора. Для тех, кто знаком с событиями инициативе архитектуры, вы можете быть заинтересованы, чтобы знать, что один из наиболее распространенных вопросов, которые мы использовали, были listen() ‘s без unlisten() ‘s (Закрытие) и отсутствует dispose() ‘S для объектов, которые создают события слушателей.

К счастью DevTools может помочь найти некоторые из этих вопросов, и Лорина Ли имеет фантастическую презентацию доступны документирования «3 снимок» техники для поиска утечек в DevTools, что я не могу рекомендовать чтение через достаточно.

Суть метода заключается в том, что вы записываете ряд действий в приложении, заставляете вывоз мусора, проверяете, не возвращается ли количество узлов DOM к ожидаемому базовому показателю, а затем анализируете три кучи снимков, чтобы определить, есть ли у вас утечка.

Управление памятью в одностраничных приложениях

Управление памятью очень важно при написании современных одностраничных приложений (например, AngularJS, Backbone, Ember), поскольку они почти никогда не обновляются. Это означает, что утечки памяти могут стать очевидными довольно быстро. Это огромная ловушка на мобильных одностраничных приложений, из-за ограниченной памяти, и на долгосрочных приложений, таких как почтовые клиенты или приложения социальных сетей. С великой силой приходит большая ответственность.

Существуют различные способы, чтобы предотвратить это. В Backbone убедитесь, что вы всегда распоряжаетесь старыми представлениями и ссылками, используя dispose() (в настоящее время доступны в Backbone (край)). Эта функция была недавно добавлена и удаляет все обработчики, добавленные в объект «событий» представления, а также любые слушатели коллекции или модели, где представление передается в качестве третьего аргумента (контекст обратного вызова). dispose()также вызывается remove() представлением, заботясь о большинстве основных потребностей очистки памяти, когда элемент очищается от экрана. Другие библиотеки, такие как Ember, убирают наблюдателей, когда они обнаруживают, что элементы были удалены из представления, чтобы избежать утечек памяти.

Некоторые мудрцы советы от Дерик Бейли:

«Помимо того, что вы знаете о том, как события работают с точки зрения ссылок, просто следуйте стандартным правилам управления памятью в JavaScript, и вы будете в порядке. Если вы загружаете данные в коллекцию Backbone, полную объектов пользователя, которую вы хотите очистить, чтобы она больше не использовала память, необходимо удалить все ссылки на коллекцию и отдельные объекты в ней. Как только вы удалите все ссылки, все будет очищено. Это просто стандартное правило сбора мусора JavaScript».

В своей статье, Derick охватывает многие из общих ловушек памяти при работе с Backbone.js и как их исправить.

Существует также полезный учебник для отладки утечек памяти в узел Феликс Гейзенндер стоит прочитать, особенно если он является частью вашего более широкого стека SPA.

Минимизация потоков

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

Chart of reflow time.
Диаграмма времени перерасхода.

Вы должны пакетные методы, которые вызывают поток или что перекрасить, и использовать их экономно. Важно, чтобы обработать DOM, где это возможно. Это возможно с помощью DocumentFragment,легкого объекта документа. Думайте об этом как о способе извлечь часть дерева документа или создать новый «фрагмент» документа. Вместо того, чтобы постоянно добавлять в DOM узлы, мы можем использовать фрагменты документов для создания всего, что нам нужно, и выполнять только одну вставку в DOM, чтобы избежать чрезмерного перелива.

Например, давайте напишем функцию, которая добавляет 20 div s к элементу. Простое присоединение каждого нового div непосредственно к элементу может вызвать 20 потоков.

function addDivs(element) {
  var div;
  for (var i = 0; i < 20; i ++) {
    div = document.createElement('div');
    div.innerHTML = 'Heya!';
    element.appendChild(div);
  }
}

Для работы над этим вопросом, мы можем DocumentFragment использовать, и вместо этого, приложить каждый из наших новых div с этим. При приложении к DocumentFragment подобному методу appendChild все дети фрагмента применяются к элементу, вызывая только один поток.

function addDivs(element) {
  var div;
  // Creates a new empty DocumentFragment.
  var fragment = document.createDocumentFragment();
  for (var i = 0; i < 20; i ++) {
    div = document.createElement('a');
    div.innerHTML = 'Heya!';
    fragment.appendChild(div);
  }
  element.appendChild(fragment);
}

Вы можете прочитать больше на этой теме на сделать стержня более быстро,
Оптимизация памяти JavaScript и поиск утечек памяти.

Детектор утечки памяти JavaScript

Чтобы помочь обнаружить утечки памяти JavaScript, двое моих коллег-гондиторов (Марья Хёлтте и Джохен Эйзингер) разработали инструмент, который работает с Chrome Developer Tools (в частности, протокол удаленного осмотра), и извлекают снимки кучи и обнаруживает объекты вызывают утечки.

A tool for detecting JavaScript memory leaks.
Инструмент для обнаружения утечек памяти JavaScript.

Там в целом сообщение о том, как использовать инструмент,и я призываю вас, чтобы проверить его или просмотреть страницу проекта утечка Finder.

Некоторые дополнительные сведения: В случае, если вам интересно, почему инструмент, как это уже не интегрированы с нашими инструментами разработчика, причина двоякая. Первоначально он был разработан, чтобы помочь нам поймать некоторые конкретные сценарии памяти в библиотеке закрытия, и это имеет больше смысла в качестве внешнего инструмента (или, может быть, даже расширение, если мы получим кучу профилирования расширения API на месте).

V8 Флаги для отладки оптимизации и сбора мусора

Chrome поддерживает прохождение ряда флагов непосредственно на V8 через js-flags флаг, чтобы получить более подробный выход о том, что двигатель оптимизирует. Например, это прослеживает v8 оптимизации:

"/Applications/Google Chrome/Google Chrome" --js-flags="--trace-opt --trace-deopt"

Пользователи Windows захотят запускатьchrome.exe –js-flags=“–trace-opt –trace-deopt”

При разработке приложения можно использовать следующие флаги V8.

  • trace-opt— журнал имена оптимизированных функций и показать, где оптимизатор пропускает код, потому что он не может понять что-то.
  • trace-deopt— войти список кода он должен был деоптимизировать во время работы.
  • trace-gc— журналы линии трассировки на каждом сборе мусора.

Скрипты обработки тиковой обработки V8 отмечают оптимизированные функции * с (звездочкой) и неоптимизированными функциями с ~ (tilde).

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

API времени высокого разрешения и навигации

Высокое время разрешения (HRT) — интерфейс JavaScript, обеспечивающий текущее время в субмиллисекундном разрешении, которое не подвержено перекосам системных часов или корректировкам пользователей. Думайте об этом как способ измерить более точно, чем мы ранее имели с new Date и Date.now() . Это полезно, когда мы пишем тесты производительности.

High Resolution Time (HRT) provides the current time in sub-millisecond resolution.
Время высокого разрешения (HRT) обеспечивает текущее время в субмиллисекундном разрешении.

HRT в настоящее время доступна в Chrome (стабильный), как window.performance.webkitNow() , но префикс упал в Chrome Canary, что делает его доступным через window.performance.now() . Пол Ирландский написал больше о HRT в пост е на HTML5Rocks.

Итак, теперь мы знаем текущее время, но что, если мы хотим API для точного измерения производительности в Интернете?

Ну, один теперь также доступен в навигации Сроки API. Этот API предоставляет простой способ получения точных и подробных измерений времени, которые записываются во время загрузки веб-страницы и представлены пользователю. Информация о сроках подвергается window.performance.timing через, которые вы можете просто использовать в консоли:

Timing information is shown in the console.
Информация о сроках отображается в консоли.

Глядя на приведенные выше данные, мы можем извлечь очень полезную информацию. Например, задержка сети — это responseEnd-fetchStart время, задеваемые для загрузки страницы после того, как она была получена от сервера, loadEventEnd-responseEnd и время, задеваемые на обработку между навигацией и загрузкой страницы. loadEventEnd-navigationStart

Как вы можете видеть выше, perfomance.memory также доступно свойство, которое дает доступ к данным об использовании памяти JavaScript, таким как общий размер кучи.

Для получения более подробной информации о навигации Сроки API, прочитайте Сэм Даттон большая статья Измерение скорость загрузки страницы с навигацией сроки.

о:память и о:отслеживание

about:tracingв Chrome предлагает интимное представление о производительности браузера, записывая все действия Chrome по всем потокам, вкладке и процессу.

about:tracing offers an intimate view of the browser’s performance.
About:Tracingпредлагает интимное представление о производительности браузера.

Что действительно полезно в этом инструменте, так это то, что он позволяет захватывать данные профилирования о том, что делает Chrome под капотом, так что вы можете правильно настроить выполнение JavaScript или оптимизировать загрузку активов.

Лилли Томпсон имеет отличную запись для разработчиков игр на использование профиля about:tracing WebGL игры. Запись также полезна для общих JavaScripters.

Навигация в about:memory Chrome также полезна, поскольку она показывает точное количество памяти, используемой каждой вкладкой, что полезно для отслеживания потенциальных утечек.

Заключение

Как мы видели, Есть много скрытых производительности gotchas в мире двигателей JavaScript, и не серебряная пуля доступна для повышения производительности. Только при объединении ряда оптимизаций в (реальной) среде тестирования можно реализовать наибольший прирост производительности. Но даже в этом случае понимание того, как двигатели интерпретируют и оптимизируют код, может дать вам представление, чтобы помочь настроить приложения.

Измерьте его. Поймите его. Исправь это. Промыть и повторить.

Measuring.
Источник изображения: Салли Хантер.

Не забывайте заботиться об оптимизации, но не стоит выбирать микро-оптимизацию за счет удобства. Например, некоторые разработчики выбирают для .forEach и Object.keys снова и for for in циклы, даже если они медленнее, для удобства быть в состоянии области. Сделайте вменяемые звонки о том, что оптимизация вашего приложения абсолютно нуждается и какие из них он может жить без.

Кроме того, имейте в виду, что, хотя двигатели JavaScript продолжают получать быстрее, следующим реальным узким местом является DOM. Reflows и перекраски так же важны, чтобы свести к минимуму, так что не забудьте коснуться DOM только, если это абсолютно необходимо. И не заботятся о сети. Запросы HTTP являются ценными, особенно на мобильных устройствах, и вы должны использовать http кэширование, чтобы уменьшить размер активов.

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

Кредиты

Эта статья была рассмотрена Якобом Куммероу, Майклом Старзингером, Синдий Сорхусом, Матиасом Байненсом, Джоном-Дэвидом Далтоном и Полом Ирландцем.

Источник изображения на первой странице.

(cp) (jc)

Источник: smashingmagazine.com

Великолепный Журнал

Великолепный, сокрушительный, разящий (см. перевод smashing) независимый журнал о веб-разработке. Основан в 2006 году в Германии. Имеет няшный дизайн и кучу крутых авторов, которых читают 2 млн человек в месяц.

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

%d такие блоггеры, как: