Подтвердить что ты не робот

Почему новый медленный?

Тест:

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));

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

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

Вопрос:

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

Update:

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

Мне интересны ссылки на спецификацию ES5 или ссылки на исходный код JagerMonkey или V8. (Не стесняйтесь связывать АО и Караканский исходный код. О, и команда IE может течь источник Чакры, если они хотят).

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

4b9b3361

Ответ 1

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

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

Абельсон и Суссман, SICP, предисловие к первому изданию

Ответ 2

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

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

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

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

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

  • 1 не выделяет новое замыкание каждый раз;

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

  • 1 не выделяет новую начальную карту для каждого нового закрытия каждый раз;

  • 1 не вызывает разметки с помощью переполнения пространства карты (только дешевые зачистки).

Если сравнить # 3 и # 4, то отличия:

  • 3 не выделяет новую начальную карту для каждого нового закрытия каждый раз;

  • 3 не вызывает разметки с помощью переполнения пространства карты (только дешевые зачистки);

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

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

Ответ 3

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

  • t (exp1): t (Создание объекта)
  • t (exp2): t (Создание объекта Object.create())
  • t (exp3): t (Создание объекта по Object.create()) + t (Создание объекта функции)
  • t (exp4): t (создание объекта) + t (создание объекта функции) + t (создание объекта класса) [в Chrome]

    • Для скрытых классов в Chrome посмотрите: http://code.google.com/apis/v8/design.html.
    • Когда новый объект создается Object.create, новый объект класса не должен быть создан. Существует уже один, который используется для литералов Object и не нуждается в новом классе.

Ответ 4

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

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 раз медленнее, даже в этом простом примере.

Ответ 5

new f;
  • взять локальную функцию 'f' (доступ по индекс в локальном фрейме) - дешево.
  • выполнить bytecode BC_NEW_OBJECT (или что-то подобное) - дешево.
  • Выполните функцию - дешево здесь.

Теперь это:

g.call(Object.create(Object.prototype));
  • Найти глобальный var Object - дешево?
  • Найти свойство prototype в Object - so-so
  • Найти свойство create в Object - so-so
  • Найти локальный var g; - дешевый
  • Найти свойство call - so-so
  • Вызывать функцию create - так себе
  • Вызывать функцию call - так себе

И это:

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

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