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

Почему Function.prototype.bind медленный?

Сравнивая этот тест с хром 16 и опера 11.6, мы обнаруживаем, что

  • в хром native bind почти в 5 раз медленнее, чем эмулированная версия bind
  • в оперативной связи bind почти в 4 раза быстрее, чем эмулированная версия bind

Если эмулированная версия привязки в этом случае

var emulatebind = function (f, context) {
    return function () {
        f.apply(context, arguments);
    };
};

Есть ли веские причины, по которым существует такая разница, или это просто вопрос v8, недостаточно оптимизирующий?

Примечание: emulatebind реализует только подмножество, но это не имеет особого значения. Если у вас полнофункциональное и оптимизированное эмулированное связывание, разница в производительности в эталонном файле все еще существует.

4b9b3361

Ответ 1

На основе http://jsperf.com/bind-vs-emulate/6, который добавляет версию es5-shim для сравнения, похоже, что виновником является дополнительная ветвь и instanceof, которую должна выполнить связанная версия, чтобы проверить, вызвана ли она как конструктор.

Каждый раз, когда запущена связанная версия, исполняемый код по существу:

if (this instanceof bound) {
    // Never reached, but the `instanceof` check and branch presumably has a cost
} else {
    return target.apply(
     that,
     args.concat(slice.call(arguments))
    );

    // args is [] in your case.
    // So the cost is:
    // * Converting (empty) Arguments object to (empty) array.
    // * Concating two empty arrays.
}

В исходном коде V8 эта проверка появляется (внутри boundFunction) как

if (%_IsConstructCall()) {
    return %NewObjectFromBound(boundFunction);
}

(ссылка Plaintext на v8natives.js, когда Google Code Search умирает.)

Немного озадачивает, что, по крайней мере, для Chrome 16 версия es5-shim все еще быстрее, чем родная версия. И что другие браузеры имеют довольно разные результаты для es5-shim против native. Спекуляция: может быть, %_IsConstructCall() еще медленнее, чем this instanceof bound, возможно, из-за пересечения границ родного/JS-кода. И, возможно, другие браузеры имеют гораздо более быстрый способ проверки вызова [[Construct]].

Ответ 2

Невозможно реализовать полнофункциональный bind только в ES5. В частности, разделы 15.3.4.5.1 до 15.3.4.5.3 спецификации не могут быть эмулированы.

15.3.4.5.1, в частности, похоже на возможное бремя производительности: в коротких связанных функциях существуют разные внутренние свойства [[Call]], поэтому их вызов, скорее всего, приведет к необычному и, возможно, более сложному коду.

Различные другие неэмулируемые функции связанной функции (например, отравление arguments/caller и, возможно, пользовательский length, не зависящий от исходной подписи), могут добавить накладные расходы для каждого вызова, хотя я допускаю это немного маловероятно. Хотя похоже, что V8 даже не реализует отравление на данный момент.

РЕДАКТИРОВАТЬ, этот ответ - это предположение, но мой другой ответ имеет нечто большее приближающееся доказательство. Я все еще думаю, что это правильное предположение, но это отдельный ответ, поэтому я оставлю его как таковой и просто отсылаю вас к другому.

Ответ 3

исходный код V8 для привязки реализован в JS.

OP не эмулирует bind, потому что он не аргументирует аргументы способом bind. Вот полнофункциональный bind:

var emulatebind = function (f, context) {
  var curriedArgs = Array.prototype.slice.call(arguments, 2);
  return function () {
    var allArgs = curriedArgs.slice(0);
    for (var i = 0, n = arguments.length; i < n; ++i) {
      allArgs.push(arguments[i]);
    }
    return f.apply(context, allArgs);
  };
};

Очевидно, что быстрая оптимизация заключалась в том, чтобы сделать

return f.apply(context, arguments);

вместо curriedArgs.length == 0, потому что в противном случае у вас есть два ненужных создания массива и ненужная копия, но, возможно, родная версия действительно реализована в JS и не делает эту оптимизацию.

Предостережение. Этот полнофункциональный bind неправильно обрабатывает некоторые угловые случаи вокруг принуждения аргумента this в строгом режиме. Это может быть еще одним источником накладных расходов.