¿Por qué arr = [] es más rápido que arr = new Array?

146

Ejecuté este código y obtuve el siguiente resultado. Tengo curiosidad por saber por qué []es más rápido?

console.time('using[]')
for(var i=0; i<200000; i++){var arr = []};
console.timeEnd('using[]')

console.time('using new')
for(var i=0; i<200000; i++){var arr = new Array};
console.timeEnd('using new')
  • usando []: 299ms
  • utilizando new: 363 ms

Gracias a Raynos aquí hay un punto de referencia de este código y alguna forma más posible de definir una variable.

ingrese la descripción de la imagen aquí

Mohsen
fuente
55
Te puede interesar jsperf .
Puntiagudo
11
Benchmark
Raynos
Tenga en cuenta la palabra clave new. Esto significa "por favor sea menos eficiente". Nunca tiene sentido, y requiere que el navegador haga la instanciación normal en lugar de intentar hacer optimizaciones.
beatgammit
2
@kinakuta no. Ambos crean nuevos objetos no iguales. Me refería []se equivelent que new Array()en términos de código fuente, no objetos devueltos expresiones de representación
Raynos
1
Sí, no es muy importante. Pero me gusta saberlo.
Mohsen

Respuestas:

195

Ampliando aún más las respuestas anteriores ...

Desde una perspectiva general de compiladores y sin tener en cuenta las optimizaciones específicas de VM:

Primero, pasamos por la fase de análisis léxico donde tokenizamos el código.

A modo de ejemplo, se pueden producir los siguientes tokens:

[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)

Esperemos que esto le proporcione una visualización suficiente para que pueda comprender cuánto se requiere más (o menos) procesamiento.

  1. Según los tokens anteriores, sabemos que ARRAY_INIT siempre producirá una matriz. Por lo tanto, simplemente creamos una matriz y la completamos. En cuanto a la ambigüedad, la etapa de análisis léxico ya ha distinguido ARRAY_INIT de un descriptor de acceso de propiedad de objeto (por ejemplo obj[foo]) o corchetes dentro de cadenas / literales de expresiones regulares (por ejemplo, "foo [] bar" o / [] /)

  2. Esto es minúsculo, pero también tenemos más tokens con new Array. Además, todavía no está del todo claro que simplemente queremos crear una matriz. Vemos el token "nuevo", pero ¿"nuevo" qué? Luego vemos el token IDENTIFICADOR que significa que queremos un nuevo "Array", pero las máquinas virtuales de JavaScript generalmente no distinguen un token IDENTIFICADOR y tokens para "objetos globales nativos". Por lo tanto...

  3. Tenemos que buscar la cadena de alcance cada vez que nos encontramos con un token IDENTIFICADOR. Las máquinas virtuales de Javascript contienen un "objeto de activación" para cada contexto de ejecución que puede contener el objeto "argumentos", variables definidas localmente, etc. Si no podemos encontrarlo en el objeto de activación, comenzamos a buscar la cadena de alcance hasta llegar al alcance global . Si no se encuentra nada, lanzamos un ReferenceError.

  4. Una vez que hemos localizado la declaración de variable, invocamos al constructor. new Arrayes una llamada de función implícita, y la regla general es que las llamadas de función son más lentas durante la ejecución (de ahí que los compiladores estáticos de C / C ++ permitan la "función en línea", que los motores JS JIT como SpiderMonkey tienen que hacer sobre la marcha)

  5. El Arrayconstructor está sobrecargado. El constructor de Array se implementa como código nativo, por lo que proporciona algunas mejoras de rendimiento, pero aún necesita verificar la longitud de los argumentos y actuar en consecuencia. Además, en el caso de que solo se proporcione un argumento, necesitamos verificar más a fondo el tipo del argumento. new Array ("foo") produce ["foo"] donde como new Array (1) produce [undefined]

Para simplificarlo todo: con los literales de matriz, la VM sabe que queremos una matriz; con new Array, la VM necesita usar ciclos de CPU adicionales para descubrir qué hace new Array realmente .

Roger Poon
fuente
no es a = nueva matriz (1000); para (de 0 a 999) {a [i] = i} más rápido que a = []; para (de 0 a 999) {a [i] = i} debido a sin embargo, gastos generales de asignación?
Y. Yoshii
Acabo de hacer un caso de prueba. La nueva matriz (n) es más rápida en los casos en que conoce el tamaño de la matriz con anticipación jsperf.com/square-braces-vs-new-array
Y. Yoshii
27

Una posible razón es que new Arrayrequiere una búsqueda de nombre Array(puede tener una variable con ese nombre en el alcance), mientras []que no.

hammar
fuente
44
Verificar los argumentos también puede contribuir.
Leonid
Arrayexceptúa tanto un argumento lencomo múltiples argumentos. Donde como []solo acepta múltiples argumentos. Además, las pruebas de Firefox no muestran casi ninguna diferencia.
Raynos
Creo que hay algo de verdad en eso. Ejecutar la prueba de bucle de OP en un IIFE tiene un impacto (relativamente) sustancial en el rendimiento. Incluyendo var Array = window.Arraymejora el rendimiento de la new Arrayprueba.
user113716
No creo que sea correcto porque este console.time ('más vars nuevo'); para (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('más vars nuevo'); más vars nuevos: 390ms y este console.time ('más vars nuevos'); var myOtherObject = {}, myOtherArray = []; para (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('más vars nuevo'); más vars nuevo: 369ms Regresa al mismo tiempo
Mohsen
2

Buena pregunta. El primer ejemplo se llama una matriz literal. Es la forma preferida de crear matrices entre muchos desarrolladores. Podría ser que la diferencia de rendimiento se deba a la comprobación de los argumentos de la nueva llamada Array () y luego a la creación del objeto, mientras que el literal crea una matriz directamente.

Creo que la diferencia relativamente pequeña en el rendimiento respalda este punto. Por cierto, puede hacer la misma prueba con el objeto y el objeto literal {}.

Laurent Zuijdwijk
fuente
1

Esto tendría sentido

Los literales de objetos nos permiten escribir código que admite muchas características y, sin embargo, lo hacen relativamente sencillo para los implementadores de nuestro código. No es necesario invocar a los constructores directamente o mantener el orden correcto de los argumentos pasados ​​a las funciones, etc.

http://www.dyn-web.com/tutorials/obj_lit.php

lnguyen55
fuente
1

Además, interesante, si la longitud de la matriz se conoce de antemano (los elementos se agregarán justo después de la creación), el uso de un constructor de matriz con una longitud especificada es mucho más rápido en Google Chrome 70+ reciente.

  • " nueva matriz ( % ARR_LENGTH% ) " - 100% (más rápido) !

  • " [] " - 160-170% (más lento)

Gráfico con resultados de las medidas.

La prueba se puede encontrar aquí: https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

Nota: este resultado probado en Google Chrome v.70 + ; en Firefox v.70 e IE ambas variantes son casi iguales.

Oleg Zarevennyi
fuente