Todos hemos escuchado que v8 usa lo que se llama clases ocultas donde cuando muchos objetos tienen la misma forma, solo almacenan un puntero a la estructura de forma que almacena compensaciones fijas. He escuchado esto un millón de veces, y entiendo mucho cómo esto reduce MUCHO el uso de memoria (no tener que almacenar un mapa para cada uno es increíble) y, potencialmente, debido a eso, el rendimiento es un poco más rápido.
Sin embargo, todavía no entiendo cómo evita la búsqueda dinámica. Lo único que he escuchado es almacenar un caché entre una cadena (nombre de campo) y un desplazamiento fijo, y verificarlo cada vez, pero si hay un error de caché (lo que es probable que suceda) todavía habrá una búsqueda dinámica.
Todo el mundo dice que esto es casi tan rápido como el acceso de campo de C++ (que por lo general son solo una instrucción mov
), sin embargo, este caché de acceso de 1 campo ni siquiera está cerca.
Mira la siguiente función:
function getx(o) { return ox; }
¿Cómo hará v8 que el acceso al campo x
sea tan rápido y evite la búsqueda dinámica?
(Desarrollador V8 aquí.)
La clave es que las clases ocultas permiten el almacenamiento en caché. Por lo tanto, ciertamente, todavía se requerirán algunas búsquedas dinámicas. Pero gracias al almacenamiento en caché, no tienen que repetirse cada vez que se ejecuta un acceso de propiedad como ox
.
Esencialmente, la primera vez que se llama a su función de ejemplo getx
, tendrá que realizar una búsqueda completa de propiedades y almacenará en caché tanto la clase oculta que se consultó como el resultado de la búsqueda. En las llamadas posteriores, simplemente verificará la clase oculta de la o
entrante con los datos almacenados en caché y, si coincide, utilizará la información almacenada en caché sobre cómo acceder a la propiedad. Por supuesto, en caso de discrepancia, tiene que recurrir de nuevo a una búsqueda dinámica. (En realidad, es un poco más complicado debido a las compensaciones adicionales que están involucradas, pero esa es la idea básica).
Entonces, si las cosas van bien, una sola búsqueda dinámica es suficiente para un número arbitrario de llamadas a dicha función. En la práctica, las cosas van tan bien que si comenzaste con un motor JS muy simple que no hizo tales trucos y agregaste el almacenamiento en caché de los resultados de búsqueda dinámica, podrías esperar una mejora de rendimiento de aproximadamente 10 veces en todos los ámbitos. Agregar un compilador de optimización puede llevar esa idea un paso más allá e incrustar los resultados almacenados en caché directamente en el código optimizado; eso puede dar fácilmente otra mejora de 10x y acercarse al rendimiento similar a C: el código aún debe contener comprobaciones de clase ocultas, pero en una comprobación exitosa puede leer propiedades con una sola instrucción. (Esta es también la razón por la cual los motores JS no pueden simplemente "optimizar todo de inmediato", incluso si quisieran: la optimización depende de manera crucial de cachés bien poblados).