una tutorial tipos pasar parametros pagina node funciones funcion ejemplos ejecutar desde cargar carga asincrona antes javascript performance v8

tutorial - pasar parametros a una funcion javascript desde html



¿Qué hace que esta función sea mucho más lenta? (3)

He estado tratando de hacer un experimento para ver si las variables locales en las funciones están almacenadas en una pila.

Entonces escribí una pequeña prueba de rendimiento

function test(fn, times){ var i = times; var t = Date.now() while(i--){ fn() } return Date.now() - t; } ene function straight(){ var a = 1 var b = 2 var c = 3 var d = 4 var e = 5 a = a * 5 b = Math.pow(b, 10) c = Math.pow(c, 11) d = Math.pow(d, 12) e = Math.pow(e, 25) } function inversed(){ var a = 1 var b = 2 var c = 3 var d = 4 var e = 5 e = Math.pow(e, 25) d = Math.pow(d, 12) c = Math.pow(c, 11) b = Math.pow(b, 10) a = a * 5 }

Esperaba que la función inversa funcionara mucho más rápido. En cambio, salió un resultado sorprendente.

Hasta que pruebo una de las funciones, se ejecuta 10 veces más rápido que después de probar la segunda.

Ejemplo:

> test(straight, 10000000) 30 > test(straight, 10000000) 32 > test(inversed, 10000000) 390 > test(straight, 10000000) 392 > test(inversed, 10000000) 390

Mismo comportamiento cuando se prueba en orden alternativo.

> test(inversed, 10000000) 25 > test(straight, 10000000) 392 > test(inversed, 10000000) 394

Lo probé tanto en el navegador Chrome como en Node.js y no tengo ni idea de por qué sucedería. El efecto dura hasta que actualice la página actual o reinicie Node REPL.

¿Cuál podría ser una fuente de rendimiento tan significativo (~ 12 veces peor)?

PD. Dado que parece funcionar solo en algunos entornos, escriba el entorno que está utilizando para probarlo.

Los míos fueron:

SO: Ubuntu 14.04
Nodo v0.10.37
Chrome 43.0.2357.134 (compilación oficial) (64 bits)

/Editar
En Firefox 39, se requieren ~ 5500 ms para cada prueba, independientemente del orden. Parece ocurrir solo en motores específicos.

/ Edit2
Al incluir la función en la función de prueba, esta se ejecuta siempre al mismo tiempo.
¿Es posible que haya una optimización que alinee el parámetro de la función si siempre es la misma función?


Al incluir la función en la función de prueba, esta se ejecuta siempre al mismo tiempo.
¿Es posible que haya una optimización que alinee el parámetro de la función si siempre es la misma función?

Sí, esto parece ser exactamente lo que estás observando. Como ya mencionó @Luaan, el compilador probablemente descarte los cuerpos de sus funciones straight e inverse todos modos porque no tienen ningún efecto secundario, sino que solo manipulan algunas variables locales.

Cuando está llamando a la test(…, 100000) por primera vez, el compilador de optimización se da cuenta después de algunas iteraciones de que la llamada fn() es siempre la misma, y ​​lo alinea, evitando la costosa llamada a la función. Todo lo que hace ahora es 10 millones de veces decrementando una variable y probándola contra 0 .

Pero cuando está llamando a test con un fn diferente, entonces, tiene que des-optimizar. Más tarde puede hacer algunas otras optimizaciones nuevamente, pero ahora sabiendo que hay dos funciones diferentes a las que se puede llamar, ya no puede alinearlas.

Dado que lo único que realmente está midiendo es la llamada a la función, eso conduce a las graves diferencias en sus resultados.

Un experimento para ver si las variables locales en las funciones se almacenan en una pila

Con respecto a su pregunta real, no, las variables individuales no se almacenan en una pila ( máquina de pila ), sino en registros ( máquina de registro ). No importa en qué orden se declaren o usen en su función.

Sin embargo, se almacenan en la pila , como parte de los llamados "marcos de pila". Tendrá un marco por llamada de función, almacenando las variables de su contexto de ejecución. En su caso, la pila podría verse así:

[straight: a, b, c, d, e] [test: fn, times, i, t] …


Estás malinterpretando la pila.

Si bien la pila "real" solo tiene las operaciones Push y Pop , esto realmente no se aplica al tipo de pila utilizada para la ejecución. Además de Push y Pop , también puede acceder a cualquier variable al azar, siempre que tenga su dirección. Esto significa que el orden de los locales no importa, incluso si el compilador no lo reordena por usted. En pseudoensamblaje, parece pensar que

var x = 1; var y = 2; x = x + 1; y = y + 1;

se traduce en algo como

push 1 ; x push 2 ; y ; get y and save it pop tmp ; get x and put it in the accumulator pop a ; add 1 to the accumulator add a, 1 ; store the accumulator back in x push a ; restore y push tmp ; ... and add 1 to y

En verdad, el código real se parece más a esto:

push 1 ; x push 2 ; y add [bp], 1 add [bp+4], 1

Si la pila de hilos realmente fuera una pila real y estricta, esto sería imposible, cierto. En ese caso, el orden de las operaciones y los locales importarían mucho más de lo que lo hace ahora. En cambio, al permitir el acceso aleatorio a los valores en la pila, ahorras mucho trabajo tanto para los compiladores como para la CPU.

Para responder a su pregunta real, sospecho que ninguna de las funciones realmente hace nada. Solo está modificando locales, y sus funciones no devuelven nada: es perfectamente legal que el compilador descarte por completo los cuerpos de las funciones, y posiblemente incluso las llamadas a funciones. Si eso es así, cualquier diferencia de rendimiento que esté observando es probablemente solo un artefacto de medición, o algo relacionado con los costos inherentes de llamar a una función / iteración.


Una vez que llame a test con dos funciones diferentes, el sitio de llamadas fn() dentro se vuelve megamórfico y V8 no puede conectarse en línea.

Las llamadas a funciones (a diferencia de las llamadas a métodos om(...) ) en V8 van acompañadas de una caché en línea de un elemento en lugar de una verdadera caché en línea polimórfica.

Debido a que V8 no puede conectarse en línea en el sitio de llamadas fn() , no puede aplicar una variedad de optimizaciones a su código. Si observa su código en IRHydra (cargué los artefactos de compilación para su conveniencia), notará que la primera versión optimizada de la test (cuando estaba especializada para fn = straight ) tiene un bucle principal completamente vacío.

V8 simplemente en línea straight y eliminó todo el código que esperaba comparar con la optimización de eliminación de código muerto. En una versión anterior de V8 en lugar de DCE, V8 simplemente levantaría el código del bucle a través de LICM, porque el código es completamente invariable.

Cuando la straight no está en línea, V8 no puede aplicar estas optimizaciones, de ahí la diferencia de rendimiento. La versión más reciente de V8 aún aplicaría DCE a sí mismos straight e inversed convirtiéndolos en funciones vacías

entonces la diferencia de rendimiento no es tan grande (alrededor de 2-3x). El V8 anterior no era lo suficientemente agresivo con DCE, y eso se manifestaría en una mayor diferencia entre los casos en línea y no en línea, porque el rendimiento máximo del caso en línea fue únicamente el resultado del movimiento agresivo de código invariante de bucle (LICM).

En una nota relacionada, esto muestra por qué los puntos de referencia nunca deben escribirse así, ya que sus resultados no son de ninguna utilidad ya que termina midiendo un bucle vacío.

Si está interesado en el polimorfismo y sus implicaciones en V8, consulte mi publicación "¿Qué pasa con el monomorfismo" (la sección "No todas las memorias caché son iguales" habla sobre las memorias caché asociadas con las llamadas a funciones). También recomiendo leer una de mis charlas sobre los peligros de la microbenchmarking, por ejemplo, la charla más reciente de "Benchmarking JS" de GOTO Chicago 2015 ( video ): podría ayudarlo a evitar las trampas comunes.