Режим оптимизации JavaScript

JavaScript

Режим оптимизации JavaScript (1) — Автор Бенедикт Мёрер

Прошло некоторое время с тех пор, как я писал в блоге, в основном потому, что у меня действительно нет ни времени, ни энергии, чтобы сесть и написать то, о чем я хочу написать. Частично причина в том, что движок браузера Chrome версии 59 V8Транслятор Ignition и компилятор TurboFan начинают работатьЭто занимало меня очень много, но, к счастью, до сих пор это было огромным успехом. Но это также отчасти потому, что я нахожу время для своей семьи. И последнее, но не менее важное: я пошелJS-конференция Европейского союза (JSConf EU)а такжеWeb RebelsДеятельность. На момент написания этого поста я участвую вenterJSдеятельности, задерживаясь на нанесении последних штрихов на мою речь.

При этом я простоBrian Terlson,Ada Rose Edwardsа такжеAshely WilliamsВернись с ужина. Мы обсудили, что есть в JavaScriptХороший режим оптимизации, я думал о том, какой совет будет наименее рискованным для других, и особенно говорил о том, как сложно придумать эти предложения, что очень интересно. В частности, я отметил, что идеальная производительность часто зависит от контекста, в котором выполняется код, и эта часть обычно самая сложная, поэтому я подумал, что, возможно, этой информацией стоит поделиться. Этот пост является началом серии постов в моем блоге, в этой первой части я хочу подчеркнуть, насколько сильно конкретная среда выполнения может повлиять на производительность кода JavaScript.

Рассмотрим следующий самодельный класс点(Point), который имеет метод, называемый距离(distance), может вычислить расстояние между двумя точкамиМанхэттенское расстояние

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  distance(other) {
    const dx = Math.abs(this.x - other.x);
    const dy = Math.abs(this.y - other.y);
    return dx + dy;
  }
}

Также рассмотрите следующее测试(test)функция драйвера, которая создает несколько点(Point)например, и вычислить расстояние между точками距离(distance)Несколько миллионов раз, добавляя результаты. Ну, я знаю, что это считается микротестом, но я расскажу об этом позже:

function test() {
  const points = [
    new Point(10, 10),
    new Point(1, 1),
    new Point(8, 9)
  ];
  let result = 0;
  for (let i = 0; i < 10000000; ++i) {
    for (const point1 of points) {
      for (const point2 of points) {
        result += point1.distance(point2);
      }
    }
  }
  return result;
}

点(Point)Этот класс, особенно его距离(distance)Теперь у метода есть подходящая эталонная функция. Давайте запустим это несколько раз测试(test)драйвер, чтобы увидеть, как он работает. Используйте следующий фрагмент кода HTML:

<script>
    function test() {
        class Point {
            constructor(x, y) {
                this.x = x;
                this.y = y;
            }

            distance(other) {
                const dx = Math.abs(this.x - other.x);
                const dy = Math.abs(this.y - other.y);
                return dx + dy;
            }
        }

        const points = [
            new Point(10, 10),
            new Point(1, 1),
            new Point(8, 9)
        ];
        let result = 0;
        for (let i = 0; i < 10000000; ++i) {
            for (const point1 of points) {
                for (const point2 of points) {
                    result += point1.distance(point2);
                }
            }
        }
        return result;
    }

    for (let i = 1; i <= 5; ++i) {
        console.time("test " + i);
        test();
        console.timeEnd("test " + i);
    }
</script>

Если вы используете браузер Chrome версии 61 (canary), вы увидите следующий вывод в терминале инструментов разработчика Chrome:

test 1: 595.248046875ms
test 2: 765.451904296875ms
test 3: 930.452880859375ms
test 4: 994.2890625ms
test 5: 3894.27392578125ms

Результаты производительности каждого раунда тестирования очень разные, и видно, что производительность ухудшается по мере продвижения. Причиной плохой работы является点(Point)Этот класс находится в测试(test)внутри функции.

<script>
    class Point {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }

        distance(other) {
            const dx = Math.abs(this.x - other.x);
            const dy = Math.abs(this.y - other.y);
            return dx + dy;
        }
    }

    function test() {
        const points = [
            new Point(10, 10),
            new Point(1, 1),
            new Point(8, 9)
        ];
        let result = 0;
        for (let i = 0; i < 10000000; ++i) {
            for (const point1 of points) {
                for (const point2 of points) {
                    result += point1.distance(point2);
                }
            }
        }
        return result;
    }

    for (let i = 1; i <= 5; ++i) {
        console.time("test " + i);
        test();
        console.timeEnd("test " + i);
    }
</script>

Давайте немного изменим этот фрагмент,点(Point)Определение класса находится в测试(test)Вне функции результат другой:

test 1: 598.794921875ms
test 2: 599.18115234375ms
test 3: 600.410888671875ms
test 4: 608.98388671875ms
test 5: 605.36376953125ms

Сейчас производительность в основном стабильная, колебания тоже в пределах нормы. Обратите внимание, что в обоих примерах点(Point)код класса и测试(test)Логика функции драйвера точно такая же. Разница только в конце点(Point)Где разместить этот класс в коде.

全局类对本地类

Также следует отметить это и новую версию ES2015.类(class)Определение синтаксиса не имеет значения. Определяется в формате синтаксиса старой версии ES5.点(Point)Классы также дают одинаковые результаты производительности:

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.distance = function (other) {
  var dx = Math.abs(this.x - other.x);
  var dy = Math.abs(this.y - other.y);
  return dx + dy;
}

когда点(Point)Сорт测试(test)Когда внутри функции будут различия в производительности, основная причина которых заключается в том, что类(class)Эта литеральная переменная выполняется несколько раз. В приведенном выше примере класс определен ровно 5 раз. И когда класс в测试(test)Находясь вне функции, определение выполняется только один раз. каждое исполнение类(class)определения создается новый объект-прототип со всеми методами этого класса. Кроме того,类(class)соответствующий новыйконструкторфункция, объект-прототип становится объектом конструктора."原型(prototype)"Атрибуты.

全局类对本地类

возьми это"原型(prototype)"Свойства действуют как объекты-прототипы, из которых генерируются новые экземпляры класса. Но поскольку двигатель V8 отслеживает прототип каждого экземпляра, рассматривайте этот прототип какформа объектаилискрытый классчасть. (Для этого см.Прототип установлен под двигатель V8, есть подробные пояснения) Чтобы оптимизировать доступ к свойствам в цепочке прототипов, разные сгенерированные прототипы естественным образом означают, что сгенерированные формы объектов также отличаются. Следовательно, когда类(class)Когда определение выполняется несколько раз, сгенерированный код становится более стабильным. В конце концов, когда V8 видит, что существует более 4 различных форм объектов, он отказывается от полиморфизма и входит в так называемыймегаморфныйstate, что означает, что он больше не генерирует высокооптимизированный код.

Таким образом, из этого упражнения мы можем узнать, что тот же самый код, просто помещенный в немного другое место, может легко вызвать6,5 разразница в производительности! Знать это чрезвычайно важно. Поскольку те широко используемые системы сравнительного анализа и веб-сайты, такие какesbench.comПри выполнении кода он, скорее всего, находится в другой среде, отличной от среды вашей программы. То есть эти сайты оборачивают свой код низкоуровневыми функциями, которые запускаются несколько раз. Это может привести к результатам тестов, которые могут ввести в заблуждение.

Двигатель V8: за кулисами (мартовское издание, основное внимание уделяется зажиганию + запуску турбовентилятора и декларативному JavaScript) Предыдущий)