Почему новый медленный? - PullRequest
30 голосов
/ 22 июня 2011

Тест:

JSPerf

Инварианты:

var f = function() { };

var g = function() { return this; }

Тесты:

Ниже в порядке ожидаемой скорости

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

Фактическая скорость:

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })

Вопрос:

  1. Когда вы меняете местами f и g для встроенных анонимных функций. Почему тест new (тест 4.) медленнее?

Обновление:

Что конкретно вызывает замедление new, когда f и g встроены.

Меня интересуют ссылки на спецификацию ES5 или ссылки на исходный код JagerMonkey или V8. (Не стесняйтесь связывать исходный код JSC и Carakan тоже. Да, и команда IE может пропустить источник Chakra, если они захотят).

Если вы связываете какой-либо источник движка JS, объясните это.

Ответы [ 5 ]

15 голосов
/ 25 июня 2011

Основное различие между # 4 и всеми другими случаями состоит в том, что первый раз, когда вы используете замыкание в качестве конструктора, всегда довольно дорогой.

  1. Он всегда обрабатывается во время выполнения V8 (не в сгенерированном коде), и переход между скомпилированным кодом JS и средой выполнения C ++ довольно дорог.Последующие распределения обычно обрабатываются в сгенерированном коде.Вы можете взглянуть на Generate_JSConstructStubHelper в builtins-ia32.cc и заметить, что он падает до Runtime_NewObject, когда у замыкания нет начальной карты.(см. http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138)

  2. Когда замыкание используется в качестве конструктора впервые, V8 должен создать новую карту (или скрытый класс) и назначить ее в качестве исходной карты об этом закрытии см. http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266. Здесь важно отметить, что карты размещаются в отдельном пространстве памяти. Это пространство не может быть очищено быстрым частичным сборщиком scavenge . Когда пространство карты переполняется, V8должен выполнить относительно дорогую полную mark-sweep GC.

Когда вы впервые используете замыкание в качестве конструктора, происходит пара других вещей, но 1 и2 являются основными причинами медлительности тестового примера № 4.

Если мы сравним выражения № 1 и № 4, то различия будут следующими:

  • # 1 не выделяет новое замыканиекаждый раз;
  • # 1 не вводит время выполнения каждый раз: после получения закрытия начальное построение карты обрабатывается по быстрому пути сгенерированного кода. Обработка всей конструкции в сгенерированном коде намного быстреезатем r, перемещаясь назад и вперед между средой выполнения и сгенерированным кодом;
  • # 1 не выделяет новую начальную карту для каждого нового замыкания каждый раз;
  • # 1 не вызывает развертки меткипереполнение пространства карты (только дешевые мусоры).

Если мы сравним # 3 и # 4, то различия будут следующие:

  • # 3 не выделяет новую начальную карту для каждогоновое закрытие каждый раз;
  • # 3 не вызывает развертки меток из-за переполнения пространства карты (только дешевые очистки);
  • # 4 делает меньше на стороне JS (без Function.prototype.call, нет Object.create, нет поиска Object.prototype и т. д.) на стороне C ++ (# 3 также входит в среду выполнения каждый раз, когда вы делаете Object.create, но очень мало там).

Нижняя строка здесьчто первый раз, когда вы используете замыкание в качестве конструктора, обходится дороже по сравнению с последующими вызовами конструирования того же замыкания , что и , потому что V8 должен настроить некоторые параметры.Если мы немедленно отбрасываем замыкание, мы в основном отбрасываем всю работу, проделанную V8 для ускорения последующих вызовов конструктора.

5 голосов
/ 25 июня 2011

Проблема в том, что вы можете проверить текущий исходный код различных движков, но это вам не сильно поможет.Не пытайтесь перехитрить компилятор.В любом случае они попытаются оптимизировать для наиболее распространенного использования.Я не думаю, что (function() { return this; }).call(Object.create(Object.prototype)), вызываемый 1000 раз, вообще имеет реальный сценарий использования.

"Программы должны быть написаны для того, чтобы люди могли их читать, и только для машин - для исполнения."

Abelson & Sussman, SICP, предисловие к первому изданию

3 голосов
/ 23 июня 2011

Я предполагаю, что следующие расширения объясняют, что происходит в V8:

  1. t (exp1): t (Создание объекта)
  2. t (exp2): t (Создание объектаby Object.create ())
  3. t (exp3): t (создание объекта с помощью Object.create ()) + t (создание объекта функции)
  4. t (exp4):t (Создание объекта) + t (Создание объекта функции) + t (Создание объекта класса) [В Chrome]

    • Для скрытых классов в Chrome смотрите: http://code.google.com/apis/v8/design.html.
    • КогдаObject создает новый объект. Создать новый объект Class не нужно.Уже есть один, который используется для литералов объектов, и нет необходимости в новом классе.
0 голосов
/ 25 июня 2011
new f;
  1. взять локальную функцию 'f' (доступ по индексу в локальном фрейме) - дешево.
  2. выполнить байт-код BC_NEW_OBJECT (или что-то подобное) - дешево.
  3. Выполните функцию - здесь дешево.

Теперь вот это:

g.call(Object.create(Object.prototype));
  1. Найти глобальный var Object - дешево?
  2. Найти недвижимость prototype в объекте - так себе
  3. найти свойство create в объекте - так себе
  4. найти локальную переменную g;- дешево
  5. Найти свойство call - так себе
  6. вызвать create функцию - так себе
  7. вызвать call функцию - так себе

И это:

new (function() { })
  1. создать новый объект функции (эта анонимная функция) - относительно дорого.
  2. выполнить байт-код BC_NEW_OBJECT - дешево
  3. Выполните функцию - дешево здесь.

Как видите, случай № 1 является наименее затратным.

0 голосов
/ 23 июня 2011

Ну, эти два вызова не делают одно и то же. Рассмотрим этот случай:

var Thing = function () {
  this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;

var Rock = function () {
  this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;

var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);

newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false

Итак, мы видим, что вызов Rock.call(otherRock) не приводит к наследованию otherRock от прототипа. Это должно учитывать как минимум некоторые из добавленных медлительности. Хотя в моих тестах конструкция new работает почти в 30 раз медленнее, даже в этом простом примере.

...