Ответ 1
Первые основы.
V8 использует скрытые классы, связанные с переходами, чтобы обнаружить статическую структуру в пушистых бесформенных объектах JavaScript.
Скрытые классы описывают структуру объекта, переходы соединяют скрытые классы вместе, описывая, какой скрытый класс следует использовать, если какое-то действие выполняется над объектом.
Например, приведенный ниже код приведет к следующей цепочке скрытых классов:
var o1 = {};
o1.x = 0;
o1.y = 1;
var o2 = {};
o2.x = 0;
o2.y = 0;
Эта цепочка создается при построении o1
. Когда o2
построено, V8 просто следует за установленными переходами.
Теперь, когда свойство fn
используется для хранения функции, V8 пытается дать этому свойству особое обращение: вместо того, чтобы просто объявлять в скрытом классе, что объект содержит свойство fn
V8 , помещает функцию в скрытый класс.
var o = {};
o.fn = function fff() { };
Теперь есть интересное следствие: если вы сохраняете разные функции в поле с тем же именем, V8 больше не может просто следовать за переходами, потому что значение свойства функции не соответствует ожидаемому значению:
var o1 = {};
o1.fn = function fff() { };
var o2 = {};
o2.fn = function ggg() { };
При оценке o2.fn = ...
присваивание V8 увидит, что есть переход с меткой fn
, но он приводит к скрытому классу, который не подходит: он содержит свойство fff
в fn
, а мы пытаемся сохранить ggg
. Примечание. Я дал имена функций только для простоты - V8 не использует их имена внутри себя, а их личность.
Поскольку V8 не может следовать этому переходу, V8 решит, что его решение о продвижении функции в скрытый класс было неправильным и расточительным. Изображение изменится
V8 создаст новый скрытый класс, где fn
- это просто свойство, а не свойство постоянной функции. Он перенаправляет переход, а также отменяет устаревшую старую цель перехода. Помните, что o1
все еще использует его. Однако следующий код времени касается o1
, например. когда свойство загружается из него - среда выполнения будет переносить o1
из устаревшего скрытого класса. Это делается для уменьшения полиморфизма - мы не хотим, чтобы o1
и o2
имели разные скрытые классы.
Почему важно иметь функции для скрытых классов? Потому что это дает V8 оптимизацию информации компилятора, которую он использует для встроенных вызовов методов. Он может обрабатывать только встроенный метод, если цель вызова хранится в самом скрытом классе.
Теперь применим это знание к приведенному выше примеру.
Так как существует столкновение между переходами bar.fn
и foo.fn
становятся нормальными свойствами - с функциями, хранящимися непосредственно на этих объектах, а V8 не может встроить вызов foo.fn
, что приведет к более низкой производительности.
Может ли он вставить вызов раньше? Да. Вот что изменилось: в старшем V8 не было механизма отмены, поэтому даже после того, как мы столкнулись и перешли на fn
переход, foo
не был перенесен в скрытый класс, где fn
становится нормальным свойством. Вместо этого foo
сохранил скрытый класс, где fn
- свойство постоянной функции, непосредственно встроенное в скрытый класс, позволяющий оптимизировать компилятор для его встроенного.
Если вы попытаетесь выбрать время bar.fn
для более старого node, вы увидите, что он медленнее:
for (var i = 0; i < 100000000; i++) {
bar.fn(); // can't inline here
}
именно потому, что он использует скрытый класс, который не позволяет оптимизировать компилятор для встроенного вызова bar.fn
.
Теперь последнее, что нужно заметить, это то, что этот тест не измеряет производительность вызова функции, а скорее измеряет, может ли оптимизатор компилятора уменьшить этот цикл до пустого цикла, введя в него вызов.