Оптимизация явной инициализации неопределенных членов объекта в JavaScript, учитывая знание внутренней работы V8/spidermonkey/chakra?
В JavaScript широко рекламируемый принцип хорошей производительности - избегать изменения формы объекта.
Это заставляет меня задуматься, это
class Foo {
constructor() {
this.bar = undefined;
}
baz(x) { this.bar = x; }
}
стоящая передовая практика, которая даст лучшую производительность, чем эта
class Foo {
constructor() {
}
baz(x) { this.bar = x; }
}
Насколько это правда или ложь? Зачем? И это более или менее верно в одном движке JS над другими?
1 ответ
Разработчик V8 здесь.
Да, первая версия - это, в общем, полезная лучшая практика.
Причина этого не в том, что создание самого объекта будет быстрее. Наоборот, довольно очевидно, что конструктор, который не выполняет никакой работы, будет работать, по крайней мере, немного быстрее, чем конструктор, выполняющий некоторую работу.
Причина, по которой рекомендуется первая версия, заключается в том, что она гарантирует, что все Foo
объекты в приложении будут иметь одинаковую "форму", тогда как со второй версией может случиться, что некоторые из них имеют .bar
собственность и другие нет. Свойства, которые иногда присутствуют, а иногда и не склонны заставлять движок JavaScript отклоняться от самых быстрых возможных состояний / путей кода, которые он может использовать; эффект будет намного больше, когда существует более одного такого свойства.
В качестве примера:
class Foo() {
constructor() {}
addBar(x) { this.bar = x; }
addBaz(x) { this.baz = x; }
addQux(x) { this.qux = x; }
}
var foo1 = new Foo(); foo1.addBar(1);
var foo2 = new Foo(); foo2.addBaz(10); foo2.addBar(2);
var foo3 = new Foo(); foo3.addQux(100); foo3.addBaz(20); foo3.addBar(3);
function hot_function(foo) {
return foo.bar; // [1]
}
hot_function(foo1);
hot_function(foo2);
hot_function(foo3);
На линии отмечены [1]
В этой версии конструктора видны объекты как минимум трех разных форм. Так что движок JavaScript найдет свойство bar
по крайней мере в трех разных местах внутри объекта. В зависимости от внутренних деталей реализации, ему, возможно, придется каждый раз искать все свойства объекта, или, может быть, он может кэшировать формы объектов, которые он видел ранее, но кэширование нескольких из них обходится дороже, чем кэширование, и будут попытки ограничения кэширования., Однако, если конструктор инициализировал все свойства undefined
тогда все входящие foo
объекты здесь будут иметь одинаковую форму, и bar
Свойство всегда будет их первым свойством, и движок может использовать действительно быстрый код для обработки этого очень простого случая.
Это не просто такие нагрузки, а то, что addBar()
В зависимости от того, может ли оно просто перезаписать существующее свойство (очень быстро), нужно ли добавить новое свойство (возможно, намного медленнее, может потребоваться выделение и копирование объекта), или оно должно динамически выбирать между обоими случаями. (медленнее, конечно).
Другой эффект заключается в том, что для каждой уникальной формы объекта требуется некоторое количество внутренних метаданных. Таким образом, избегая ненужных различных форм объекта, вы сэкономите память.
Конечно, для такого маленького примера любой эффект будет небольшим. Но если у вас есть большое приложение с тысячами объектов с десятками свойств каждый, оно может иметь действительно большое значение. Остерегайтесь вводящих в заблуждение микробенчмарков!