Почему Function.prototype.bind работает медленно? - PullRequest
32 голосов
/ 28 декабря 2011

При сравнении этого теста с Chrome 16 против Opera 11.6 мы находим, что

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

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

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

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

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

Ответы [ 3 ]

27 голосов
/ 07 января 2012

На основании 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);
}

( Открытая текстовая ссылка на v8natives.js , когда умирает Поиск кода Google.)

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

7 голосов
/ 07 января 2012

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

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

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

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

3 голосов
/ 06 января 2012

Исходный код V8 для bind реализован в 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 приведение аргументов в строгом режиме.Это может быть еще одним источником накладных расходов.

...