Ответ 1
Первые основы о V8 и jsPerf:
-
В V8 используется метод, называемый скрытыми классами. Каждый скрытый класс описывает определенную форму объекта, например. объект имеет свойство
x
при смещении16
или объект имеет методf
, и эти скрытые классы связаны вместе с переходами, поскольку объект мутирован, образуя деревья перехода (которые, строго говоря, dags). Не все скрытые классы находятся в одном и том же дереве перехода; вместо этого из каждого конструктора берется новое дерево перехода. Посмотрите эти слайды, чтобы понять основную идею скрытых классов. -
Когда jsPerf выполняет следующие тесты: заданные
setup
иbody
, он несколько раз генерирует и запускает функцию, выглядящую примерно так:function measure() { /* setup */ var start = Date.now(); for (var i = 0; i < N; i++) { /* body */ } var end = Date.now(); /* N / (start - end) determines ops / ms reported */ }
Каждый запуск называется образцом.
Теперь давайте взглянем на деревья перехода в вашем тесте.
-
Скрытый класс
o
принадлежит дереву перехода с корнем в конструктореo
. Обратите внимание, что каждый конструктор вызывается один раз. Это позволяет V8 строить следующее дерево перехода в памяти:O{} -f-> O{ f: <closure> }
Скрытый класс
o
по существу говорит V8, чтоo
имеет метод, называемыйf
, реализованный данным закрытием. Это позволяет компилятору, оптимизирующему V8, встроитьf
в ваш тест выше, что по существу делает цикл бенчмаркинга пустым. -
Скрытый класс
o2
принадлежит дереву переходаObject
. Сначала обратите внимание, чтоsetup
вызывается несколько раз, поэтому, если V8 попытался применить ту же оптимизацию с продвижениемf
к методу, который он прибудет в дерево невозможного перехода:Object{} -f-> Object{ f: <closure> } -f-> Object{ f: <other closure> }
На самом деле V8 даже не пытается. Разработчики V8 предвидели эту ситуацию, и V8 просто делает
f
нормальным свойством:Object{} -f-> Object{ f: <property at offset 8> }
Таким образом, для вызова
o2.f()
ему необходимо сначала загрузить его, и это также ухудшает вложение.
Здесь вы должны понимать одну важную вещь: если вы дважды вызываете конструктор o
, тогда V8 прибудет в одно и то же дерево невозможного перехода, которое V8 избегает удара для Object
:
O{} -f-> O{ f: <closure> }
-f-> O{ f: <other closure> }
У вас не может быть такой структуры. В этом случае V8 на лету преобразует f
в поле вместо того, чтобы сделать его методом и переписывает дерево перехода:
O{} -f-> O{ f: <property at offset 8> }
Смотрите этот эффект в http://jsperf.com/function-call-on-js-objects/3, где я добавил один new O()
, прежде чем создавать o
. Вы заметите, что производительность объектного литерала и объекта, построенного с помощью new
, одинаковы.
Еще одна деталь заключается в том, что V8 попытается превратить f
в метод для литерала, если литерал объявлен в глобальной области. См. http://jsperf.com/function-call-on-js-objects/5 и Проблема 2246 против V8. Обоснование здесь простое: литерал в глобальной области оценивается только один раз, поэтому вероятность того, что такое продвижение будет успешным, и не будет конфликтов между методами, которые возникли бы, если литерал оценивается несколько раз.
Вы можете узнать больше о похожих проблемах в моем сообщении в блоге.