El rendimiento asociado con matrices y objetos en JavaScript (especialmente Google V8) sería muy interesante de documentar. No encuentro ningún artículo completo sobre este tema en Internet.
Entiendo que algunos Objetos usan clases como su estructura de datos subyacente. Si hay muchas propiedades, ¿a veces se trata como una tabla hash?
También entiendo que las matrices a veces se tratan como matrices C ++ (es decir, indexación aleatoria rápida, eliminación lenta y cambio de tamaño). Y, otras veces, se tratan más como Objetos (indexación rápida, inserción / remoción rápida, más memoria). Y, tal vez a veces se almacenan como listas vinculadas (es decir, indexación aleatoria lenta, eliminación / inserción rápida al principio / final)
¿Cuál es el rendimiento preciso de las recuperaciones y manipulaciones de matrices / objetos en JavaScript? (específicamente para Google V8)
Más específicamente, cuál es el impacto en el rendimiento de:
- Agregar una propiedad a un objeto
- Eliminar una propiedad de un objeto
- Indexar una propiedad en un objeto
- Agregar un elemento a una matriz
- Eliminar un elemento de una matriz
- Indexación de un elemento en una matriz
- Llamando a Array.pop ()
- Llamar a Array.push ()
- Llamar a Array.shift ()
- Llamar a Array.unshift ()
- Llamando a Array.slice ()
También se agradecería cualquier artículo o enlace para obtener más detalles. :)
EDITAR: Realmente me pregunto cómo funcionan las matrices y objetos de JavaScript bajo el capó. Además, ¿en qué contexto el motor V8 "sabe" "cambiar" a otra estructura de datos?
Por ejemplo, supongamos que creo una matriz con ...
var arr = [];
arr[10000000] = 20;
arr.push(21);
¿Qué está pasando realmente aquí?
O ... ¿qué hay de esto ... ???
var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
var item = arr[i].shift();
//Do something with item...
}
Para las matrices convencionales, el rendimiento sería terrible; mientras que, si se utilizó una LinkedList ... no tan mal.
fuente
Respuestas:
Creé un conjunto de pruebas, precisamente para explorar estos problemas (y más) ( copia archivada ).
Y en ese sentido, puede ver los problemas de rendimiento en este probador de más de 50 casos de prueba (llevará mucho tiempo).
Además, como sugiere su nombre, explora el uso de la naturaleza de lista enlazada nativa de la estructura DOM.
(Actualmente inactivo, reconstruido en progreso) Más detalles en mi blog al respecto .
El resumen es el siguiente
Array.shift()
es rápido ~ aproximadamente 6 veces más lento que una matriz emergente, pero es ~ aproximadamente 100 veces más rápido que la eliminación de un atributo de objeto.Array.push( data );
es más rápido queArray[nextIndex] = data
en casi 20 (matriz dinámica) a 10 (matriz fija) veces.Array.unshift(data)
es más lento como se esperaba, y es ~ aproximadamente 5 veces más lento que la adición de una nueva propiedad.array[index] = null
es más rápido que eliminarlodelete array[index]
(indefinido) en una matriz ~ aproximadamente 4x ++ más rápido.obj[attr] = null
~ aproximadamente 2 veces más lento que simplemente eliminar el atributodelete obj[attr]
Array.splice(index,0,data)
es lenta, muy lenta.Array.splice(index,1,data)
se ha optimizado (sin cambio de longitud) y es 100 veces más rápido que solo empalmarArray.splice(index,0,data)
dll.splice(index,1)
eliminación (donde rompió el sistema de prueba).Nota: estas métricas se aplican solo a grandes conjuntos / objetos que la versión 8 no "optimiza completamente". Puede haber casos de rendimiento optimizado muy aislados para un tamaño de matriz / objeto menor que un tamaño arbitrario (24?). Se pueden ver más detalles en varios videos de Google IO.
Nota 2: Estos maravillosos resultados de rendimiento no se comparten entre navegadores, especialmente
*cough*
IE. Además, la prueba es enorme, por lo tanto, todavía tengo que analizar y evaluar completamente los resultados: edítelo en =)Nota actualizada (diciembre de 2012): los representantes de Google tienen videos en youtubes que describen el funcionamiento interno de Chrome (como cuando cambia de una matriz de lista enlazada a una matriz fija, etc.) y cómo optimizarlos. Consulte GDC 2012: de la consola a Chrome para obtener más información.
fuente
En un nivel básico que permanece dentro de los dominios de JavaScript, las propiedades de los objetos son entidades mucho más complejas. Puede crear propiedades con setters / getters, con diferentes enumerabilidad, capacidad de escritura y configurabilidad. Un elemento de una matriz no se puede personalizar de esta manera: existe o no. En el nivel del motor subyacente, esto permite una mayor optimización en términos de organización de la memoria que representa la estructura.
En términos de identificar una matriz de un objeto (diccionario), los motores JS siempre han hecho líneas explícitas entre los dos. Es por eso que hay una multitud de artículos sobre métodos para intentar hacer un objeto similar a una matriz semi-falso que se comporte como uno pero que permita otras funciones. La razón por la que esta separación incluso existe es porque los propios motores JS almacenan los dos de manera diferente.
Las propiedades se pueden almacenar en un objeto de matriz, pero esto simplemente demuestra cómo JavaScript insiste en convertir todo en un objeto. Los valores indexados en una matriz se almacenan de manera diferente a cualquier propiedad que decida establecer en el objeto de matriz que representa los datos de la matriz subyacente.
Siempre que esté utilizando un objeto de matriz legítimo y use uno de los métodos estándar para manipular esa matriz, estará golpeando los datos de la matriz subyacente. Específicamente en V8, estos son esencialmente lo mismo que un arreglo de C ++, por lo que se aplicarán esas reglas. Si por alguna razón está trabajando con una matriz que el motor no puede determinar con confianza es una matriz, entonces se encuentra en un terreno mucho más inestable. Sin embargo, con las versiones recientes de V8 hay más espacio para trabajar. Por ejemplo, es posible crear una clase que tenga Array.prototype como su prototipo y aún así obtener un acceso eficiente a los diversos métodos nativos de manipulación de matrices. Pero este es un cambio reciente.
Los enlaces específicos a cambios recientes en la manipulación de matrices pueden resultar útiles aquí:
Como un poco más, aquí está Array Pop y Array Push directamente desde la fuente de V8, ambos implementados en JS mismo:
fuente
Me gustaría complementar las respuestas existentes con una investigación a la pregunta de cómo se comportan las implementaciones con respecto a las matrices en crecimiento: si las implementan de la manera "habitual", se verían muchos impulsos rápidos con impulsos lentos raros e intercalados, momento en el que la implementación se copia la representación interna de la matriz de un búfer a uno más grande.
Puedes ver este efecto muy bien, esto es de Chrome:
Aunque se perfila cada impulso, la salida contiene solo aquellos que toman tiempo por encima de un cierto umbral. Para cada prueba, personalicé el umbral para excluir todos los impulsos que parecen representar los impulsos rápidos.
Entonces, el primer número representa qué elemento se ha insertado (la primera línea es para el elemento 17), el segundo es el tiempo que tomó (para muchas matrices, el punto de referencia se realiza en paralelo) y el último valor es la división de la primer número por el de la línea anterior.
Todas las líneas que tienen un tiempo de ejecución inferior a 2 ms están excluidas para Chrome.
Puede ver que Chrome aumenta el tamaño de la matriz en potencias de 1.5, más un desplazamiento para tener en cuenta las matrices pequeñas.
Para Firefox, es una potencia de dos:
Tuve que subir un poco el umbral en Firefox, por eso comenzamos en el n. ° 126.
Con IE, obtenemos una combinación:
Es una potencia de dos al principio y luego se mueve a potencias de cinco tercios.
Entonces, todas las implementaciones comunes usan la forma "normal" para las matrices (en lugar de volverse locas con cuerdas , por ejemplo).
Aquí está el código de referencia y aquí está el violín .
fuente
Mientras corría bajo node.js 0.10 (construido en v8), estaba viendo un uso de CPU que parecía excesivo para la carga de trabajo. Rastreé un problema de rendimiento en una función que estaba verificando la existencia de una cadena en una matriz. Entonces hice algunas pruebas.
Cargar 91k entradas en una matriz (con validar y empujar) es más rápido que configurar obj [clave] = valor.
En la siguiente prueba, busqué cada nombre de host en la lista una vez (91k iteraciones, para promediar el tiempo de búsqueda):
La aplicación aquí es Haraka (un servidor SMTP) y carga el host_list una vez al inicio (y después de los cambios) y posteriormente realiza esta búsqueda millones de veces durante la operación. Cambiar a un objeto fue una gran ganancia de rendimiento.
fuente