Por qué es la Función.prototipo.¿enlazar despacio?


Al comparar este punto de referencia con chrome 16 vs opera 11.6 encontramos que

  • en chrome native bind es casi 5 veces más lento que una versión emulada de bind
  • en opera native bind es casi 4 veces más rápido que una versión emulada de bind

Donde una versión emulada de bind en este caso es

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

¿Hay buenas razones por las que hay tal diferencia o es solo una cuestión de que v8 no optimice lo suficiente?

Nota: que emulatebind solo implementa un subconjunto, pero eso no es realmente relevante. Si tiene un enlace emulado completamente equipado y optimizado, la diferencia de rendimiento en el índice de referencia sigue existiendo.

Author: Raynos, 2011-12-28

3 answers

Basado en http://jsperf.com/bind-vs-emulate/6 , que agrega la versión es5-shim para la comparación, parece que el culpable es la rama extra y instanceof que la versión enlazada tiene que realizar para probar si está siendo llamada como un constructor.

Cada vez que se ejecuta la versión enlazada, el código que se ejecuta es esencialmente:

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.
}

En el código fuente V8 , esta comprobación aparece (dentro de boundFunction) como

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

(Enlace de texto plano a v8natives.js para cuando la búsqueda de código de Google muere.)

Es un poco desconcertante que, para Chrome 16 al menos, la versión es5-shim sigue siendo más rápido que la versión nativa. Y que otros navegadores tienen resultados bastante variables para es5-shim vs. nativo. Especulación: tal vez %_IsConstructCall() es incluso más lento que this instanceof bound, tal vez debido a cruzar los límites del código nativo/JS. Y tal vez otros navegadores tienen una forma mucho más rápida de comprobar si hay una llamada [[Construct]].

 27
Author: Domenic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-06 21:10:10

Es imposible implementar un bind completo solo en ES5. En secciones particulares15.3.4.5.1 hasta 15.3.4.5.3 de la especificación no puede ser emulado.

15.3.4.5.1, en particular, parece una posible carga de rendimiento: en funciones de enlace corto tienen diferentes propiedades internas [[Call]], por lo que llamarlas es probable que tome una ruta de código inusual y posiblemente más complicada.

Varias otras características específicas no emulables de una función enlazada (tales como arguments/caller envenenamiento, y posiblemente la costumbre length independiente de la firma original) posiblemente podría agregar sobrecarga a cada llamada, aunque admito que es un poco improbable. Aunque parece que V8 ni siquiera implementa el envenenamiento en este momento.

EDITAR esta respuesta es especulación, pero mi otra respuesta tiene algo más cercano a la evidencia. Todavía creo que esto es una especulación válida, pero es una respuesta separada, así que lo dejaré como tal y solo lo referiré a la el otro.

 7
Author: Domenic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-08 16:38:20

El código fuente V8 para bind está implementado en JS.

El OP no emula bind porque no curry argumentos como bind lo hace. Aquí está un completo 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);
  };
};

Obviamente, una optimización rápida sería hacer

return f.apply(context, arguments);

En cambio si curriedArgs.length == 0, porque de lo contrario tiene dos creaciones de matrices innecesarias, y una copia innecesaria, pero tal vez la versión nativa está realmente implementada en JS y no hace esa optimización.

Advertencia: Este completo bind no maneja correctamente algunos casos de esquina alrededor de this argumento coerción en modo estricto. Eso podría ser otra fuente de gastos generales.

 3
Author: Mike Samuel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-06 19:46:49