¿Por qué el bind es más lento que un cierre?


Un póster anterior pidió Función.bind vs Closure en Javascript: ¿cómo elegir?

Y recibió esta respuesta en parte, que parece indicar que el enlace debe ser más rápido que un cierre:

Scope traversal significa, cuando se está alcanzando para agarrar un valor (variable, objeto) que existe en un ámbito diferente, por lo tanto se agrega sobrecarga adicional (el código se vuelve más lento de ejecutar).

Usando bind, está llamando a una función con un ámbito existente, por lo que que el recorrido del alcance no tiene lugar.

Dos jsperfs sugieren que el enlace es en realidad mucho, mucho más lento que un cierre.

Esto fue publicado como un comentario a lo anterior

Y decidí escribir mi propio jsperf

Entonces, ¿por qué el enlace es mucho más lento (70+% en cromo)?

Dado que no es más rápido y los cierres pueden servir para el mismo propósito, se debe evitar el enlace?

Author: Community, 2013-07-14

1 answers

Chrome 59 actualización: Como predije en la respuesta a continuación bind ya no es más lento con el nuevo compilador de optimización. Aquí está el código con detalles: https://codereview.chromium.org/2916063002 /

La mayoría de las veces no importa.

A menos que esté creando una aplicación donde .bind es el cuello de botella, no me molestaría. La legibilidad es mucho más importante que el rendimiento en la mayoría de los casos. Creo que usar .bind nativo generalmente proporciona más legible y código mantenible-que es una gran ventaja.

Sin embargo sí, cuando importa - .bind es más lento

Sí, .bind es considerablemente más lento que un cierre - al menos en Chrome, al menos en la forma actual se implementa en v8. He tenido que cambiar de nodo.JS para problemas de rendimiento algunas veces (más generalmente, los cierres son un poco lentos en situaciones de rendimiento intensivo).

¿Por qué? Porque el algoritmo .bind es mucho más complicado que envolver una función con otra función y usando .call o .apply. (Dato curioso, también devuelve una función con toString establecida en [función nativa]).

Hay dos maneras de ver esto, desde el punto de vista de la especificación, y desde el punto de vista de la implementación. Observemos ambos.

Primero, veamos el algoritmo bind definido en la especificación :

  1. Sea Target el valor this.
  2. Si IsCallable (Target) es false, lanza un TypeError salvedad.
  3. Sea A una nueva lista interna (posiblemente vacía) de todos los valores de argumento proporcionados después de thisArg (arg1, arg2, etc.), en orden.

...

(21. Llame al método interno[[DefineOwnProperty]] de F con argumentos "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, y false.

(22. Volver F.

Parece bastante complicado, mucho más que envolver.

En segundo lugar , vamos a ver cómo se implementa en Chrome.

Vamos a comprobar FunctionBind en el código fuente v8 (motor JavaScript chrome):

function FunctionBind(this_arg) { // Length is 1.
  if (!IS_SPEC_FUNCTION(this)) {
    throw new $TypeError('Bind must be called on a function');
  }
  var boundFunction = function () {
    // Poison .arguments and .caller, but is otherwise not detectable.
    "use strict";
    // This function must not use any object literals (Object, Array, RegExp),
    // since the literals-array is being used to store the bound data.
    if (%_IsConstructCall()) {
      return %NewObjectFromBound(boundFunction);
    }
    var bindings = %BoundFunctionGetBindings(boundFunction);

    var argc = %_ArgumentsLength();
    if (argc == 0) {
      return %Apply(bindings[0], bindings[1], bindings, 2, bindings.length - 2);
    }
    if (bindings.length === 2) {
      return %Apply(bindings[0], bindings[1], arguments, 0, argc);
    }
    var bound_argc = bindings.length - 2;
    var argv = new InternalArray(bound_argc + argc);
    for (var i = 0; i < bound_argc; i++) {
      argv[i] = bindings[i + 2];
    }
    for (var j = 0; j < argc; j++) {
      argv[i++] = %_Arguments(j);
    }
    return %Apply(bindings[0], bindings[1], argv, 0, bound_argc + argc);
  };

  %FunctionRemovePrototype(boundFunction);
  var new_length = 0;
  if (%_ClassOf(this) == "Function") {
    // Function or FunctionProxy.
    var old_length = this.length;
    // FunctionProxies might provide a non-UInt32 value. If so, ignore it.
    if ((typeof old_length === "number") &&
        ((old_length >>> 0) === old_length)) {
      var argc = %_ArgumentsLength();
      if (argc > 0) argc--;  // Don't count the thisArg as parameter.
      new_length = old_length - argc;
      if (new_length < 0) new_length = 0;
    }
  }
  // This runtime function finds any remaining arguments on the stack,
  // so we don't pass the arguments object.
  var result = %FunctionBindArguments(boundFunction, this,
                                      this_arg, new_length);

  // We already have caller and arguments properties on functions,
  // which are non-configurable. It therefore makes no sence to
  // try to redefine these as defined by the spec. The spec says
  // that bind should make these throw a TypeError if get or set
  // is called and make them non-enumerable and non-configurable.
  // To be consistent with our normal functions we leave this as it is.
  // TODO(lrn): Do set these to be thrower.
  return result;

Podemos ver un montón de cosas caras aquí en la implementación. A saber, %_IsConstructCall(). Por supuesto, esto es necesario para cumplir con la especificación , pero también lo hace más lento que una simple envoltura en muchos casos.


En otra nota, llamar a .bind también es ligeramente diferente, las notas de especificación "Function objects created using Function.prototipo.bind no tiene una propiedad prototype ni las propiedades internas [[Code]], [[FormalParameters]] y [[Scope]]"

 129
Author: Benjamin Gruenbaum,
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
2017-10-18 10:18:34