Por que arr = [] é mais rápido que arr = new Array?

146

Eu corri esse código e obtive o resultado abaixo. Estou curioso para saber por que []é mais 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
  • usando new: 363ms

Graças a Raynos, aqui está uma referência deste código e uma maneira mais possível de definir uma variável.

insira a descrição da imagem aqui

Mohsen
fonte
5
Você pode estar interessado em jsperf .
Pointy
11
Referência
Raynos
Observe a palavra-chave new. Isso significa "por favor, seja menos eficiente". Isso nunca faz sentido e exige que o navegador execute a instanciação normal em vez de tentar fazer otimizações.
Beatgammit 10/09/11
2
@kinakuta no. Ambos criam novos objetos não iguais. Eu quis dizer []é equivelent para new Array()em termos de código-fonte, não objetos retornados expressões de formulário
Raynos
1
Sim, não é muito importante. Mas eu gosto de saber.
Mohsen

Respostas:

195

Expandindo ainda mais as respostas anteriores ...

De uma perspectiva geral dos compiladores e desconsiderando as otimizações específicas da VM:

Primeiro, passamos pela fase de análise lexical em que tokenizamos o código.

A título de exemplo, os seguintes tokens podem ser produzidos:

[]: 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)

Espero que isso ofereça uma visualização suficiente para que você possa entender quanto mais (ou menos) processamento é necessário.

  1. Com base nos tokens acima, sabemos que ARRAY_INIT sempre produzirá uma matriz. Portanto, simplesmente criamos uma matriz e a preenchemos. Quanto à ambiguidade, o estágio de análise lexical já distinguiu ARRAY_INIT de um acessador de propriedade de objeto (por exemplo obj[foo]) ou colchetes dentro de strings / regex literais (por exemplo, "foo [] bar" ou / [] /)

  2. Isso é minúsculo, mas também temos mais tokens new Array. Além disso, ainda não está totalmente claro que simplesmente queremos criar uma matriz. Vemos o "novo" token, mas "novo" o que? Em seguida, vemos o token IDENTIFIER, o que significa que queremos uma nova "matriz", mas as VMs do JavaScript geralmente não distinguem um token e tokens IDENTIFIER para "objetos globais nativos". Portanto...

  3. Temos que procurar a cadeia de escopo sempre que encontrarmos um token IDENTIFIER. As VMs Javascript contêm um "Objeto de ativação" para cada contexto de execução que pode conter o objeto "argumentos", variáveis ​​definidas localmente etc. Se não pudermos encontrá-lo no objeto Ativação, começaremos a procurar a cadeia de escopo até atingir o escopo global . Se nada for encontrado, jogamos a ReferenceError.

  4. Depois de localizar a declaração da variável, chamamos o construtor. new Arrayé uma chamada de função implícita, e a regra geral é que as chamadas de função são mais lentas durante a execução (por isso, os compiladores estáticos de C / C ++ permitem a "função embutida" - que mecanismos JS JIT, como o SpiderMonkey, precisam fazer on-the-fly)

  5. O Arrayconstrutor está sobrecarregado. O construtor Array é implementado como código nativo, portanto fornece alguns aprimoramentos de desempenho, mas ainda precisa verificar o comprimento dos argumentos e agir de acordo. Além disso, no caso de apenas um argumento ser fornecido, precisamos verificar ainda mais o tipo do argumento. new Array ("foo") produz ["foo"] onde, como new Array (1) produz [indefinido]

Então, para simplificar tudo: com literais de matriz, a VM sabe que queremos uma matriz; com new Array, a VM precisa usar ciclos extras de CPU para descobrir o que new Array realmente faz.

Roger Poon
fonte
não é a = nova matriz (1000); para (de 0 a 999) {a [i] = i} mais rápido que a = []; para (de 0 a 999) {a [i] = i} devido a despesas gerais de alocação?
Y. Yoshii
Acabei de fazer um caso de teste. nova matriz (n) é mais rápida nos casos em que você sabe o tamanho da matriz antecipadamente jsperf.com/square-braces-vs-new-array
Y. Yoshii
27

Um possível motivo é que new Arrayrequer uma pesquisa de nome Array(você pode ter uma variável com esse nome no escopo), enquanto []isso não acontece.

hammar
fonte
4
A verificação de argumentos também pode contribuir.
Leonid
Arrayexcede um argumento lene vários argumentos. Onde []apenas aceita múltiplos argumentos. Também os testes do Firefox mostram quase nenhuma diferença.
Raynos 10/09/11
Eu acho que há alguma verdade nisso. A execução do teste de loop do OP em um IIFE causa um impacto (relativamente) substancial no desempenho. A inclusão var Array = window.Arraymelhora o desempenho do new Arrayteste.
user113716
Eu não acho que está certo, porque este console.time ('more vars new'); for (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); mais vars new: 390ms e este console.time ('more vars new'); var myOtherObject = {}, myOtherArray = []; for (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); mais vars novo: 369ms Retorna ao mesmo tempo
Mohsen
2

Boa pergunta. O primeiro exemplo é chamado de matriz literal. É a maneira preferida de criar matrizes entre muitos desenvolvedores. Pode ser que a diferença de desempenho seja causada verificando os argumentos da nova chamada Array () e criando o objeto, enquanto o literal cria uma matriz diretamente.

A diferença relativamente pequena no desempenho confirma esse ponto, eu acho. Você pode fazer o mesmo teste com o objeto e o literal do objeto {} a propósito.

Laurent Zuijdwijk
fonte
1

Isso faria algum sentido

Literais de objetos nos permitem escrever código que suporta muitos recursos, mas ainda assim o torna relativamente simples para os implementadores de nosso código. Não há necessidade de chamar construtores diretamente ou manter a ordem correta dos argumentos transmitidos às funções, etc.

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

lnguyen55
fonte
1

Além disso, interessante, se o comprimento da matriz for conhecido antecipadamente (os elementos serão adicionados logo após a criação), o uso de um construtor de matriz com um comprimento especificado será muito mais rápido no recente Google Chrome 70+.

  • " nova matriz ( % ARR_LENGTH% ) " - 100% (mais rápido) !

  • " [] " - 160-170% (mais lento)

Gráfico com os resultados das medidas.

O teste pode ser encontrado aqui - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

Nota: este resultado foi testado no Google Chrome v.70 + ; no Firefox v.70 e IE ambas as variantes são quase iguais.

Oleg Zarevennyi
fonte