2011-12-28 18 views
32

Al comparar this benchmark con Chrome 16 vs Opera 11.6 encontramos que¿Por qué Function.prototype.bind es lento?

  • en lazo nativo de Chrome es casi 5 veces más lento que una versión emulada de bind
  • en la ópera se unen nativa es casi 4 veces más rápido que un emulado versión de bind

Cuando 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 cuales existe tal diferencia o es solo una cuestión de v8 no optimizar lo suficiente?

Nota: emulatebind solo implementa un subconjunto pero eso no es realmente relevante. Si tiene un enlace emulado optimizado y con todas las funciones, el performance difference in the benchmark aún existe.

+0

@RobW introdujo la versión emulada de bind, la estoy comparando. – Raynos

+0

Supongo que esto se debe a una optimización diferente del código. Tal vez, el contenedor con enlace nativo no permite algunas optimizaciones determinadas. FF10 demuestra el comportamiento similar. – kirilloid

+3

Su Q. debe ser _ "¿Por qué mi emulado .bind() es más rápido que un nativo en Chrome, FireFox y más lento en Opera e IE?" _. Y por qué piensas que debe ser de otra manera? Diferente optimización de código. Su enlace emulado no permite agregar parámetros, sino solo contexto, por ejemplo. –

Respuesta

26

Basado en http://jsperf.com/bind-vs-emulate/6, lo que añade la versión ES5-cuña para la comparación, parece que el culpable es la rama extra y instanceof que la versión encuadernada tiene que llevar a cabo para probar si está siendo llamado como un constructor.

Cada vez que la versión encuadernada se ejecuta, 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. 
} 

In the V8 source code, esta comprobación aparece (en el interior boundFunction) como

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

(Plaintext link to v8natives.js para cuando Google Code Buscar muere.)

Es un poco desconcertante que, para Chrome 16 al menos, la versión es5-shim es aún más rápido que el na versión interactiva. Y que otros navegadores tienen resultados bastante variables para es5-shim vs. native. Especulación: quizás %_IsConstructCall() es aún más lento que this instanceof bound, quizás debido al cruce de límites de código nativo/JS. Y tal vez otros navegadores tengan una manera mucho más rápida de buscar una llamada [[Construct]].

+1

[instancia de verificación adicional a raynosBound] (http://jsperf.com/bind-vs-emulate/7). Creo que la sobrecarga es principalmente concatenando matrices vacías. ¿Crees que vale la pena optimizar a mano el ESL bind shim para el caso 'args.length === 0' para devolver una función que solo hace' target.apply (that, arguments) '? – Raynos

+3

Personalmente, creo que cualquier optimización aquí es microoptimización, y esperaría hasta que mis puntos de referencia mostraran que las funciones vinculadas se estaban superando, p.latencia de red al hacer que mi aplicación sea lenta antes de considerar algo por el estilo. – Domenic

3

El V8 source code for bind se implementa en JS.

El OP no emula bind porque no curry argumentos como lo hace bind. 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); 

lugar si curriedArgs.length == 0, porque de lo contrario tiene dos creaciones de matriz innecesarios, y una copia innecesaria, pero quizás la versión nativa realmente se implementa en JS y no hace esa optimización.

Advertencia: Esta función completa bind no maneja correctamente algunos corner cases alrededor de this argumento de coerción en modo estricto. Esa podría ser otra fuente de gastos generales.

+0

No emula bind completamente. Pero las pruebas de rendimiento solo usan el subconjunto de las partes de bind que se emulan. ¿Puedes explicar por qué el parámetro currying causa una sobrecarga cuando no se utiliza en las pruebas de rendimiento? ¿Puedes editar las pruebas con una completa? – Raynos

+0

[La diferencia de rendimiento aún existe] (http://jsperf.com/bind-vs-emulate/4) – Raynos

+0

@Raynos, agregó un enlace al código fuente JS para la función de enlace de 'v8natives.js'. –

7

Es imposible implementar un bind con todas las funciones solo en ES5. En particular, las secciones 15.3.4.5.1 a 15.3.4.5.3 de la especificación no se pueden emular.

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

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

EDIT esta respuesta es especulación, pero mi otra respuesta tiene algo más que acercar evidencia. Sigo pensando que esto es una especulación válida, pero es una respuesta separada, así que lo dejo como tal y solo lo remito al otro.

Cuestiones relacionadas