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.