Проблема с производительностью, вызванная Vue

JavaScript Vue.js

Автор недавно представил библиотеку анимации в проекте Vue, но обнаружил, что производительность немного ненормальная. ЦП, используемый в проекте, примерно в 3,5 раза больше, чем у демонстрационной страницы. Я удалил все другие мешающие вещи в проекте. Но ЦП просто не может снизиться.Как показано на рисунке ниже, нормальный диапазон колеблется в районе 2,1%:

Но когда его вводят в проект, становится колебание около 7%:

Может ли это быть из-за того, что вложенность html слишком глубокая, что усложняет Layout и другие вычисления, поэтому увеличивается нагрузка на процессор?Автор попытался упростить структуру DOM и добавить методы изоляции Layout, такие как contains:strict, но это не сработало. Таким образом, это может быть только проблема выполнения JS, которую можно изучить с помощью инструментов разработки Chrome.

Как показано ниже:

Плотные строки выше — это обратные вызовы requestAnimationFrame, увеличьте его, а затем просмотрите обратный вызов, чтобы сравнить разницу между демонстрационной страницей и страницей Vue, как показано на следующем рисунке:

Здесь хорошо видна разница: время выполнения каждого обратного вызова в demo.html составляет около 0,3 мс, в то время как время выполнения обратного вызова проекта Vue составляет около 0,8 мс, что почти в 3 раза быстрее, а стек вызовов намного Глубже. Что это за дополнительные вещи? Взгляните повнимательнее:

Эти штуки есть во Vue, то есть сеттеры во Vue, а некоторые обратные вызовы тоже содержат геттеры во Vue:

В это время я вдруг понял, что из-за того, что геттер/сеттер переменной был переписан во Vue, получение свойства или переписывание свойства занимало много времени, из-за чего поднимался процессор. Причина, по которой Vue переписан, заключается в том, что переменная библиотеки анимации используется как свойство this в компоненте в коде, как показано в следующем коде:

import Player from 'player.js';

export default {
  data: {
    return {
      player: new Player()
    };
  }
};

Затем Vue пройдёт по объекту игрока и добавит сеттеры/геттеры ко всем свойствам, как показано на следующем управляющем выводе:

Здесь Ir.set — это скриншот из производительности выше, что замедляет установку переменной Ii. Здесь мы заметили деталь, консоль Chrome будет напрямую печатать Object, который не покрывает сеттер/геттер, и если он установлен, он будет заменен на "(...)", а затем ждать, пока вы не нажмете, чтобы получить Отображается текущее значение.

Как видно из исходного кода Vue, Vue определяет установщик и получатель свойства для переменных-членов:

// 代码有所删减
function defineReactive?1 (obj, key, val) {
  var dep = new Dep();
  
  var property = Object.getOwnPropertyDescriptor(obj, key);
  // 从源码也可以看到,可以把obj的configurable置为false,Vue便不会设置getter和setter
  if (property && property.configurable === false) {
    return
  }
  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      return value
    },
    set: function reactiveSetter (newVal) { 
      var value = getter ? getter.call(obj) : val;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      dep.notify();
    }
  });
}

Чтобы сделать некоторые уведомления, когда пользователь устанавливает значение, чтобы достичь цели, управляемой данными. Но в то же время это также может вызвать проблемы с производительностью, в этом примере время вызова увеличивается примерно на 0,3 мс. На самом деле это время почти ничтожно, но так как этот пример нужно запускать в requestAnimationFrame, то он вызывается 60 раз в секунду, что относительно часто.Исходное время было всего 0,2 мс, но теперь оно увеличилось на 0,3 мс из-за к этому сеттеру/геттеру. , более чем в два раза больше обычного времени, поэтому ЦП увеличивается.

Зная причину, можно решить проблему Текущее решение состоит не в том, чтобы рассматривать переменную player как свойство-член в this, а в том, чтобы получить ее снаружи, как показано в следующем коде:

import Player from 'player.js';
let player = new Player();

export default {
  data: {
    return {
    };
  }
};

(Дополнительно: вы также можете видеть из исходного кода Vue, что установка для настраиваемого свойства объекта значения false также может решить проблему.)

В это время загрузка ЦП упала с 7% до примерно 4%, почти вдвое, как показано на следующем рисунке:

Глядя на стек вызовов сеттера в Performance, больше нет, как показано на следующем рисунке:

Но CPU по-прежнему в два раза больше, чем у демо-страницы (2% и 4%), а пока продолжаем проверять стек вызовов и обнаруживаем, что время вызова функции одной ji в два раза больше, чем другой:

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

Объединение нескольких операторов в один в сжатом коде также должно повысить производительность.

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


Кроме того, это исследование заставляет задуматься над интересным вопросом,? Если я пишу цикл для записи, использование ЦП должно быть 100% (он заполняет одно ядро), как показано в следующем коде:

let now = Date.now();
// 跑个50s
while (Date.now() - now < 50000);

В это время загрузка ЦП составляет 100%:

Если я позволю ему заснуть на 50 мс, то просушу еще 50 мс, попеременно, как показано в коде ниже:

function sleep (time) {
  return new Promise(resolve => {
    setTimeout(resolve, time);
  });
}
let now = Date.now();
async function start () {
  while (Date.now() - now < 50000) {
    // 睡50ms
    await sleep(50);
    let current = Date.now();
    // 干50ms
    while (Date.now() - current < 50);
  }
  console.log('end');
}
start();

В это время загрузка ЦП будет колебаться около 50%, как показано ниже:

Разве это не интересно?