función lenta llamada en V8 cuando se utiliza la misma tecla para las funciones en diferentes objetos


Tal vez no porque la llamada sea lenta, sino que la búsqueda lo es; no estoy seguro, pero aquí hay un ejemplo:

var foo = {};
foo.fn = function() {};

var bar = {};
bar.fn = function() {};

console.time('t');

for (var i = 0; i < 100000000; i++) {
    foo.fn();
}

console.timeEnd('t');

Probado en win8.1

  • firefox 35.01: ~240ms
  • chrome 40.0.2214.93 (V8 3.30.33.15): ~760ms
  • msie 11: 34 seg
  • nodejs 0.10.21 (V8 3.14.5.9): ~100ms
  • iojs 1.0.4 (V8 4.1.0.12): ~760ms

Ahora aquí está la parte interesante, si cambio bar.fn a bar.somethingelse:

  • chrome 40.0.2214.93 (V8 3.30.33.15): ~100ms
  • nodejs 0.10.21 (V8 3.14.5.9): ~100ms
  • iojs 1.0.4 (V8 4.1.0.12): ~100ms

Algo salió mal en v8 últimamente? ¿Qué causa esto?

Author: Totoro, 2015-01-28

2 answers

Primeros fundamentos.

V8 utiliza clases ocultas conectadas con transiciones para descubrir la estructura estática en los objetos JavaScript esponjosos y sin forma.

Las clases ocultas describen la estructura del objeto, las transiciones vinculan las clases ocultas entre sí describiendo qué clase oculta se debe usar si se realiza una determinada acción en un objeto.

Por ejemplo, el siguiente código conduciría a la siguiente cadena de ocultos clases:

var o1 = {};
o1.x = 0;
o1.y = 1;
var o2 = {};
o2.x = 0;
o2.y = 0;

introduzca la descripción de la imagen aquí

Esta cadena se crea a medida que construyes o1. Cuando se construye o2 V8 simplemente sigue las transiciones establecidas.

Ahora, cuando se usa una propiedad fn para almacenar una función V8 intenta darle a esta propiedad un tratamiento especial: en lugar de simplemente declarar en la clase oculta que el objeto contiene una propiedad fn V8 pone la función en la clase oculta.

var o = {};
o.fn = function fff() { };

introduzca la descripción de la imagen aquí

Ahora hay un interesante consecuencia aquí: si almacena diferentes funciones en el campo con el mismo nombre V8 ya no puede seguir simplemente las transiciones porque el valor de la propiedad function no coincide con el valor esperado:

var o1 = {};
o1.fn = function fff() { };
var o2 = {};
o2.fn = function ggg() { };

Al evaluar o2.fn = ... assignment V8 verá que hay una transición etiquetada fn pero conduce a una clase oculta que no es adecuada: contiene fff en la propiedad fn, mientras que estamos tratando de almacenar ggg. Nota: He dado nombres de funciones solo por simplicidad - V8 no utiliza internamente sus nombres sino su identidad.

Debido a que V8 no puede seguir esta transición, V8 decidirá que su decisión de promover la función a la clase oculta fue incorrecta y derrochadora. La imagen cambiará

introduzca la descripción de la imagen aquí

V8 creará una nueva clase oculta donde fn es solo una propiedad simple y ya no una propiedad de función constante. Redireccionará la transición y también marcará el destino de transición antiguo obsoleto. Recuerde que o1 todavía lo está usando. Sin embargo, la próxima vez que el código toque o1, por ejemplo, cuando se carga una propiedad desde él, runtime migrará o1 de la clase oculta obsoleta. Esto se hace para reducir el polimorfismo - no queremos que o1 y o2 tengan diferentes clases ocultas.

¿Por qué es importante tener funciones en las clases ocultas? Debido a que esto proporciona la información del compilador de optimización de V8 que utiliza para llamadas a métodos inline. Solo puede llamar al método en línea si llama al destino se almacena en la propia clase oculta.

Ahora vamos a aplicar este conocimiento al ejemplo anterior.

Debido a que hay un choque entre las transiciones bar.fn y foo.fn se convierten en propiedades normales, con funciones almacenadas directamente en esos objetos y V8 no puede alinear la llamada de foo.fn lo que lleva a un rendimiento más lento.

¿Podría insertar la llamada antes? . Esto es lo que cambió: en V8 anterior no había mecanismo de desaprobación así que incluso después de que tuvimos un choque y la transición redireccionada fn, foo no se migró a la clase oculta donde fn se convierte en una propiedad normal. En su lugar, foo todavía mantuvo la clase oculta donde fn es una propiedad de función constante directamente incrustada en la clase oculta que permite al compilador de optimización insertarla.

Si intentas timing bar.fn en el nodo anterior verás que es más lento:

for (var i = 0; i < 100000000; i++) {
    bar.fn();  // can't inline here
}       

Precisamente porque usa clase oculta que no permite optimizar el compilador a inline bar.fn llamada.

Ahora bien, lo último que hay que notar aquí es que este punto de referencia no mide el rendimiento de una llamada a una función, sino que mide si el compilador de optimización puede reducir este bucle a un bucle vacío insertando la llamada dentro de él.

 63
Author: Vyacheslav Egorov,
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
2015-01-28 21:27:08

Los literales de objetos comparten clases ocultas ("map" en términos internos de v8) por estructura, es decir, claves con el mismo nombre en el mismo orden, mientras que los objetos creados a partir de diferentes constructores tendrían diferentes clases ocultas incluso si los constructores los inicializaran a exactamente los mismos campos.

Al generar código para foo.fn(), en el compilador generalmente no tiene acceso al objeto foo específico, sino solo a su clase oculta. Desde la clase oculta se puede acceder a la función fn pero porque el la clase oculta compartida puede tener una función diferente en la propiedad fn, eso no es posible. Así que como no sabes en tiempo de compilación qué función se llamará, no puedes insertar la llamada.

Si ejecuta el código con trace inlining flag:

$ /c/etc/iojs.exe --trace-inlining test.js
t: 651ms

Sin embargo, si cambia algo para que .fn sea siempre la misma función o foo y bar tengan una clase oculta diferente:

$ /c/etc/iojs.exe --trace-inlining test.js
Inlined foo.fn called from .
t: 88ms

(Hice esto haciendo bar.asd = 3 antes de el bar.fn-asignación, pero hay un montón de formas diferentes de lograrlo, como constructores y prototipos que seguramente sabes que son el camino a seguir para javascript de alto rendimiento)

Para ver qué ha cambiado entre las versiones, ejecute este código:

var foo = {};
foo.fn = function() {};

var bar = {};
bar.fn = function() {};

foo.fn();
console.log("foo and bare share hidden class: ", %HaveSameMap(foo, bar));

Como se puede ver los resultados difieren entre node10 y iojs:

$ /c/etc/iojs.exe --allow-natives-syntax test.js
foo and bare share hidden class:  true

$ node --allow-natives-syntax test.js
foo and bare share hidden class:  false

No he seguido el desarrollo de v8 en detalles recientemente, así que no pude señalar la razón exacta, pero este tipo de heurística cambia todo el tiempo en general.

IE11 es de código cerrado, pero de todo lo que han documentado en realidad parece que es muy similar a v8.

 28
Author: Esailija,
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
2015-01-28 20:50:34