Os loops são realmente mais rápidos ao contrário?


268

Já ouvi isso algumas vezes. Os loops JavaScript são realmente mais rápidos quando contados para trás? Se sim, por quê? Eu já vi alguns exemplos de suítes de teste mostrando que os loops invertidos são mais rápidos, mas não consigo encontrar nenhuma explicação sobre o porquê!

Suponho que seja porque o loop não precisa mais avaliar uma propriedade cada vez que verifica se está concluído e apenas verifica o valor numérico final.

Ou seja,

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}

6
ele Ele. isso vai demorar indefinidamente. tentei--
StampedeXV

5
O forloop para trás é mais rápido porque a variável de controle do loop com limite superior (hehe, limite inferior) não precisa ser definida ou buscada em um objeto; é um zero constante.
21139 Josh Stodola

88
Não há diferença real . As construções de loop nativo sempre serão muito rápidas . Não se preocupe com o desempenho deles.
precisa

24
@ Afshin: Para perguntas como essa, mostre-nos os artigos aos quais você está se referindo.
Lightness Races in Orbit -

22
Há diferenças importantes principalmente para dispositivos de gama baixa e alimentados por bateria. As diferenças são que com i-- você compara com 0 no final do loop, enquanto com i ++ você compara com número> 0. Acredito que a diferença de desempenho seja algo como 20 nanossegundos (algo como cmp ax, 0 vs. cmp ax , bx) - que não é nada, mas se você laço milhares de vezes por segundo, por que não ter 20 nanossegundos ganhar para cada :)
Luben

Respostas:


903

Não é que i--seja mais rápido que i++. Na verdade, ambos são igualmente rápidos.

O que leva tempo em loops ascendentes é avaliar, para cada um i, o tamanho da sua matriz. Neste loop:

for(var i = array.length; i--;)

Você avalia .lengthapenas uma vez, quando declara i, enquanto que para esse loop

for(var i = 1; i <= array.length; i++)

você avalia .lengthcada vez que incrementa i, quando verifica sei <= array.length .

Na maioria dos casos, você não deve se preocupar com esse tipo de otimização .


23
vale a pena introduzir uma variável para array.length e usá-la na cabeça do loop for?
Ragatskynet 30/10/12

39
@ragatskynet: Não, a menos que você esteja configurando uma referência e queira fazer uma observação.
31412 Jon

29
@ragatskynet Depende: será mais rápido avaliar .lengthum certo número de vezes ou declarar e definir uma nova variável? Na maioria dos casos, isso é otimização prematura (e incorreta), a menos que sua avaliação lengthseja muito cara.
Alestanis

10
@ Dr.Dredel: Não é a comparação - é a avaliação. 0é mais rápido avaliar do que array.length. Bem, supostamente.
Lightness Races em órbita

22
O que vale a pena mencionar é que estamos falando de linguagens interpretadas como Ruby, Python. Linguagens compiladas, por exemplo, Java possuem otimizações no nível do compilador, o que "suaviza" essas diferenças a ponto de não importar se .lengthestá em declaração for loopou não.
Haris Krajina

198

Esse cara comparou muitos loops em javascript, em muitos navegadores. Ele também tem uma suíte de testes para que você possa executá-los você mesmo.

Em todos os casos (a menos que eu tenha perdido uma na minha leitura), o loop mais rápido foi:

var i = arr.length; //or 10
while(i--)
{
  //...
}

Bom :) Mas não existe um teste "for" invertido para trás ... Mas os loops for mencionados por peirix ou searlea devem ser praticamente os mesmos que o loop "while" com "i--" como sua condição. E esse foi o loop mais rápido testado.
SvenFinke

11
Interessante, mas me pergunto se o pré-decréscimo seria ainda mais rápido. Como ele não precisará armazenar o valor intermediário de i.
Tvanfosson 27/08/2009

Se bem me lembro, meu professor de cursos de hardware disse que esse teste para 0 ou não 0 é o "cálculo" mais rápido possível. Enquanto (i--) o teste é sempre um teste para 0. Talvez seja por isso que seja mais rápido?
StampedeXV

3
@tvanfosson se você pré-decremento --i, em seguida, você tem que usar var current = arr[i-1];dentro do loop ou ele vai estar fora por uma ...
DaveAlger

2
Ouvi dizer que i-- pode ser mais rápido que --i porque, no segundo caso, o processador precisa diminuir e testar contra o novo valor (há uma dependência de dados entre as instruções), enquanto no primeiro caso, o processador pode teste o valor existente e diminua o valor algum tempo depois. Não tenho certeza se isso se aplica ao JavaScript ou apenas ao código C de nível muito baixo.
Marcus

128

Eu tento dar uma imagem ampla com esta resposta.

A opinião a seguir entre parênteses era minha opinião, até que recentemente testei o problema:

[[Em termos de linguagens de baixo nível, como C / C ++ , o código é compilado para que o processador tenha um comando de salto condicional especial quando uma variável for zero (ou diferente de zero).
Além disso, se você se preocupa com tanta otimização, pode ir em ++ivez de i++, porque ++ié um comando de processador único, enquanto i++significa j=i+1, i=j.]]

Loops muito rápidos podem ser feitos desenrolando-os:

for(i=800000;i>0;--i)
    do_it(i);

Pode ser muito mais lento que

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}

mas as razões para isso podem ser bastante complicadas (apenas para mencionar, existem os problemas de pré-processamento de comandos do processador e manipulação de cache no jogo).

Em termos de linguagens de alto nível , como o JavaScript solicitado, você pode otimizar as coisas se confiar em bibliotecas, funções internas para loop. Deixe-os decidir como é feito melhor.

Consequentemente, em JavaScript, eu sugeriria usar algo como

array.forEach(function(i) {
    do_it(i);
});

Também é menos propenso a erros e os navegadores podem otimizar seu código.

[OBSERVAÇÃO: não apenas os navegadores, mas você também tem um espaço para otimizar com facilidade, basta redefinir a forEachfunção (navegador dependente) para que ele use os melhores truques mais recentes! :) @AMK diz que em casos especiais vale a pena usar array.popor array.shift. Se você fizer isso, coloque-o atrás da cortina. O maior exagero é adicionar opções forEachpara selecionar o algoritmo de loop.]

Além disso, também para linguagens de baixo nível, a melhor prática é usar alguma função de biblioteca inteligente para operações complexas em loop, se possível.

Essas bibliotecas também podem colocar coisas (multiencadeadas) nas suas costas e também programadores especializados as mantêm atualizadas.

Fiz um exame mais minucioso e, em C / C ++, mesmo para operações 5e9 = (50.000 x 100.000), não há diferença entre subir e descer se o teste for feito com uma constante como o @alestanis diz. (Os resultados do JsPerf às vezes são inconsistentes, mas geralmente dizem o mesmo: você não pode fazer uma grande diferença.)
Por --iisso, é uma coisa "chique". Isso só faz você parecer um programador melhor. :)

Por outro lado, ao desenrolar-se nessa situação 5e9, reduzi-me de 12 para 2,5 segundos quando passei 10s e para 2,1 segundos quando passei 20 anos. Foi sem otimização, e a otimização reduziu as coisas a pouco tempo imensurável. :) (O desenrolar pode ser feito da minha maneira acima ou usando i++, mas isso não traz as coisas adiante no JavaScript.)

Em suma: mantenha i--/ i++e ++i/ i++diferenças nas entrevistas de emprego, mantenha array.forEachou outras funções complexas da biblioteca, quando disponíveis. ;)


18
A palavra-chave é "pode ​​ser". Seu loop desenrolado também pode ser mais lento que o original. Ao otimizar, sempre meça para saber exatamente o impacto que suas alterações tiveram.
jalf

1
@ jalf verdade, +1. Comprimentos diferentes de loop (> = 1) são diferentes em eficiência. É por isso que é mais conveniente deixar esse trabalho para as bibliotecas, se possível, sem mencionar que os navegadores são executados em arquiteturas diferentes, para que seja melhor se eles decidirem como fazê-lo array.each(...). Eu não acho que eles tentariam experimentar loops simples e simples.
Barney:

1
@BarnabasSzabolcs A pergunta é para JavaScript especificamente, não para C ou outros idiomas. Em JS lá é uma diferença, por favor, veja a minha resposta abaixo. Embora não seja aplicável à pergunta, marque com +1 uma boa resposta!
AMK

1
E lá vai você - +1para conseguir um Great Answerdistintivo. Realmente ótima resposta. :)
Rohit Jain

1
APL / A ++ também não é uma linguagem. As pessoas as usam para expressar que não estão interessadas em detalhes específicos de idiomas, mas em algo que esses idiomas compartilham.
Barney27 /

33

i-- é tão rápido quanto i++

Este código abaixo é tão rápido quanto o seu, mas usa uma variável extra:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

A recomendação é NÃO avaliar o tamanho da matriz a cada vez. Para matrizes grandes, pode-se ver a degradação do desempenho.


6
Você está claramente errado aqui. Agora, o controle de loop precisa de uma variável interna extra (ie para cima) e, dependendo do mecanismo JavaScript, isso pode limitar o potencial de otimização do código. Os JITs converterão loops simples em códigos de operação diretos da máquina, mas se os loops tiverem muitas variáveis, o JIT não poderá otimizar tão bem. Geralmente, é limitado pela arquitetura ou pelos registros da CPU que o JIT usa. Inicializando uma vez e descendo simplesmente a solução mais limpa e é por isso que é recomendado em todo o lugar.
Pavel P

A variável adicional será eliminada pelo otimizador de um interpretador / compilador comum. O ponto é o 'compare com 0' - veja minha resposta para mais explicações.
H.-Dirk Schmitt

1
Melhor colocar 'up' dentro do loop:for (var i=0, up=Things.length; i<up; i++) {}
thdoan

22

Como você está interessado no assunto, dê uma olhada no blog de Greg Reimer sobre um benchmark de loop JavaScript: Qual é a maneira mais rápida de codificar um loop em JavaScript? :

Criei um conjunto de testes de benchmarking de loop para diferentes maneiras de codificar loops em JavaScript. Já existem alguns deles por aí, mas não encontrei nenhum que reconhecesse a diferença entre matrizes nativas e coleções HTML.

Você também pode fazer um teste de desempenho em um loop abrindo https://blogs.oracle.com/greimer/resource/loop-test.html(não funciona se o JavaScript estiver bloqueado no navegador por, por exemplo, NoScript ).

EDITAR:

Uma referência mais recente criada por Milan Adamovsky pode ser executada em tempo de execução aqui para diferentes navegadores.

Para um teste no Firefox 17.0 no Mac OS X 10.6 , recebi o seguinte loop:

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}

como o mais rápido precedido por:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}

Exemplo de referência.


5
Esse post tem 4 anos agora. Dada a velocidade (ha!) Em que os mecanismos JavaScript foram atualizados, a maioria dos conselhos provavelmente será obsoleta - o artigo nem menciona o Chrome e é o brilhante mecanismo V8 JavaScript.
Yi Jiang

Sim, sinto falta desses detalhes, obrigado por apontar isso. Evento naquele momento não havia muita diferença entre --i ou i ++ no loop for.
dreamcrash 30/10/12

19

Não é o --ou ++, é a operação de comparação. Com --você pode usar uma comparação com 0, enquanto com ++você precisa compará-lo com o comprimento. No processador, comparar com zero normalmente está disponível, enquanto comparar com um número inteiro finito requer uma subtração.

a++ < length

é realmente compilado como

a++
test (a-length)

Por isso, leva mais tempo no processador quando compilado.


15

Resposta curta

Para código normal, especialmente em uma linguagem de alto nível como JavaScript , não há diferença de desempenho em i++e i--.

O critério de desempenho é o uso no forloop e a comparação instrução .

Isso se aplica a todas as linguagens de alto nível e é principalmente independente do uso de JavaScript. A explicação é o código do assembler resultante na linha inferior.

Explicação detalhada

Uma diferença de desempenho pode ocorrer em um loop. O pano de fundo é que, no nível do código do assembler , você pode ver que a compare with 0é apenas uma instrução que não precisa de um registro adicional.

Essa comparação é emitida em cada passagem do loop e pode resultar em uma melhoria mensurável do desempenho.

for(var i = array.length; i--; )

será avaliado para um pseudo-código como este:

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END

Observe que 0 é um valor literal ou, em outras palavras, um valor constante.

for(var i = 0 ; i < array.length; i++ )

será avaliado para um pseudo-código como este (otimização normal do interpretador suposta):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END

Observe que end é uma variável que precisa de um registro da CPU . Isso pode chamar uma troca de registro adicional no código e precisa de uma instrução de comparação mais cara na ifinstrução.

Apenas meus 5 centavos

Para uma linguagem de alto nível, a legibilidade, que facilita a manutenção, é mais importante como uma pequena melhoria no desempenho.

Normalmente, a iteração clássica do início ao fim da matriz é melhor.

A iteração mais rápida do final da matriz ao início resulta na sequência inversa possivelmente indesejada.

Post Scriptum

Conforme solicitado em um comentário: A diferença de --ie i--está na avaliação dei antes ou depois da diminuição.

A melhor explicação é experimentá-lo ;-) Aqui está um exemplo do Bash .

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9

1
1+ boa explicação. Apenas uma pergunta um pouco fora do escopo, você poderia explicar a diferença entre --ie i--também?
Afshin Mehrabani

14

Eu vi a mesma recomendação no Sublime Text 2.

Como já foi dito, a principal melhoria não é avaliar o comprimento da matriz em cada iteração no loop for. Essa é uma técnica de otimização conhecida e particularmente eficiente em JavaScript quando a matriz faz parte do documento HTML (fazendo um forpara todos osli elementos).

Por exemplo,

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

é muito mais lento que

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

De onde eu estou, a principal melhoria no formulário da sua pergunta é o fato de ela não declarar uma variável extra ( lenno meu exemplo)

Mas se você me perguntar, o ponto principal não é sobre a otimização i++vs i--, mas sobre não ter que avaliar o comprimento da matriz em cada iteração (você pode ver um teste de benchmark no jsperf ).


1
Eu tenho que fazer uma exceção à palavra cálculo aqui. Veja meu comentário sobre a resposta de Pavel . A especificação da ECMA afirma que o comprimento da matriz não é calculado quando você se refere a ela.
Kojiro #

'Avaliar' seria uma escolha melhor? Fato interessante btw, eu não sabia que
BBog

A variável adicional será eliminada pelo otimizador de um interpretador / compilador comum. O mesmo se aplica à avaliação array.length. O ponto é o 'compare com 0' - veja minha resposta para mais explicações.
H.-Dirk Schmitt

@ H.-DirkSchmitt, deixe de lado sua resposta de bom senso, por muito tempo na história do JavaScript, o compilador não otimizou o custo de desempenho da cadeia de pesquisa. O AFAIK V8 foi o primeiro a tentar.
Kojiro # 31/12

@ H.-DirkSchmitt, como Kojiro disse, este é um truque bem conhecido e bem estabelecido. Mesmo que não seja mais relevante nos navegadores modernos, isso ainda não o torna obsoleto. Além disso, fazê-lo com a introdução de uma nova variável por tamanho ou com o truque na pergunta do OP, ainda é a melhor maneira, imo. É apenas uma coisa inteligente a se fazer e boas práticas, acho que não tem nada a ver com o fato de o compilador ter sido otimizado para cuidar de algo frequentemente feito de maneira ruim em JS
BBog

13

Não acho que faça sentido dizer que i--é mais rápido do que i++em JavaScript.

Primeiro de tudo , depende totalmente da implementação do mecanismo JavaScript.

Segundo , desde que as construções mais simples sejam JIT'ed e traduzidas para instruções nativas, o i++vs i--dependerá totalmente da CPU que a executa. Ou seja, nos ARMs (telefones celulares) é mais rápido descer para 0, pois o decremento e a comparação a zero são executados em uma única instrução.

Provavelmente, você pensou que uma era desperdiçada que a outra, porque a maneira sugerida é

for(var i = array.length; i--; )

mas o caminho sugerido não é porque um mais rápido que o outro, mas simplesmente porque se você escrever

for(var i = 0; i < array.length; i++)

então, em cada iteração, array.lengthera necessário avaliar (um mecanismo JavaScript mais inteligente talvez pudesse descobrir que o loop não mudaria o comprimento da matriz). Mesmo que pareça uma declaração simples, na verdade é uma função chamada pelo mecanismo de JavaScript.

A outra razão pela qual i--poderia ser considerado "mais rápido" é porque o mecanismo JavaScript precisa alocar apenas uma variável interna para controlar o loop (variável para o var i). Se você comparou com array.length ou com alguma outra variável, era necessário que houvesse mais de uma variável interna para controlar o loop, e o número de variáveis ​​internas é um recurso limitado de um mecanismo JavaScript. Quanto menos variáveis ​​forem usadas em um loop, mais chances o JIT terá de otimização. É por isso que i--pode ser considerado mais rápido ...


3
Provavelmente vale a pena elaborar frases cuidadosas sobre como array.lengthé avaliada. O comprimento não é calculado quando você se refere a ele. (É apenas uma propriedade que é definida sempre que um índice de matriz é criado ou alterado ). Quando há custo adicional, é porque o mecanismo JS não otimizou a cadeia de pesquisa para esse nome.
Kojiro # 30/12

Bem, não sei o que a especificação da Ecma diz, mas conhecer getLength(){return m_length; }algumas partes internas de diferentes mecanismos JavaScript não é simples, porque há algumas tarefas domésticas envolvidas. Mas se você tentar pensar para trás: que seria bastante engenhosa para implementação de array write onde o comprimento precisaria ser realmente calculado :)
Pavel P

a especificação do ECMA exige que a propriedade length já esteja calculada. Ele lengthdeve ser atualizado imediatamente sempre que um índice de propriedade que é um array for adicionado ou alterado.
Kojiro # 30/12

O que estou tentando dizer é que é muito difícil violar as especificações se você tentar pensar sobre isso.
Pavel P

1
x86 é como ARM nesse aspecto. dec/jnzvs. inc eax / cmp eax, edx / jne.
Peter Cordes

13

Como nenhuma das outras respostas parece responder à sua pergunta específica (mais da metade delas mostra exemplos de C e discute idiomas de nível inferior, sua pergunta é para JavaScript), decidi escrever minha própria.

Então, aqui vai você:

Resposta simples: i-- geralmente é mais rápida, porque não é necessário executar uma comparação com 0 cada vez que é executada, os resultados dos testes em vários métodos estão abaixo:

Resultados do teste: Como "comprovado" por este jsPerf, arr.pop()é na verdade o loop mais rápido de longe. Mas, focando --i, i--, i++e++i como você pediu na sua pergunta, aqui estão JSPerf (eles são de múltipla JSPerf de, por favor veja fontes abaixo) resultados resumidos:

--ie i--são iguais no Firefox, enquanto i--é mais rápido no Chrome.

No Chrome, um loop for básico ( for (var i = 0; i < arr.length; i++)) é mais rápido que i--e--i enquanto no Firefox é mais lento.

No Chrome e no Firefox, um cache arr.length é significativamente mais rápido, com o Chrome à frente em cerca de 170.000 ops / s.

Sem uma diferença significativa, ++ié mais rápido quei++ na maioria dos navegadores, o AFAIK, nunca é o contrário em nenhum navegador.

Resumo mais curto: arr.pop() é o loop mais rápido de longe; para os loops especificamente mencionados,i-- é o loop mais rápido.

Fontes: http://jsperf.com/fastest-array-loops-in-javascript/15 , http://jsperf.com/ipp-vs-ppi-2

Espero que isso responda à sua pergunta.


1
Parece que seu popteste parece muito rápido, porque está reduzindo o tamanho da matriz para 0 na maior parte do loop - até onde eu sei. No entanto, para garantir que as coisas sejam justas, projetei esse jsperf para criar a matriz da mesma maneira a cada teste - o que parece .shift()ser o vencedor dos meus poucos navegadores - não o que eu esperava :) jsperf.com / compare-tipos-diferentes-de-loop
Pebbl

Voto por ser o único a mencionar ++i: D
jaggedsoft 4/16/16

12

Depende do posicionamento do seu array na memória e da taxa de acerto das páginas da memória enquanto você está acessando esse array.

Em alguns casos, o acesso aos membros da matriz na ordem das colunas é mais rápido que na ordem das linhas, devido ao aumento na taxa de acertos.


1
Se apenas o OP tinha perguntado por que atravessa a mesma matriz em ordens diferentes podem assumir diferentes quantidades de tempo ..
dcow

1
Considerando que nos sistemas operacionais o gerenciamento de memória deles é baseado em paginação, quando um processo precisa de dados que não estão em páginas em cache, ocorre uma falha de página no sistema operacional e deve trazer a página de destino para o cache da CPU e substituí-la por outra página; causa sobrecarga no processamento que leva mais tempo do que quando a página de destino está no cache da CPU. Suponha que definimos uma matriz grande em que cada linha seja maior que o tamanho do sistema operacional da página e a acessemos na ordem das linhas; nesse caso, a taxa de falhas da página aumenta e o resultado é mais lento que o acesso da ordem da coluna a essa matriz.
Akbar Bayati

As falhas de página não são a mesma coisa que as falhas de cache. Você só falha na página se sua memória foi paginada em disco. O acesso a matrizes multidimensionais em ordem seqüencial de como eles são armazenados na memória é mais rápido devido à localidade do cache (usando todos os bytes de uma linha de cache quando buscada), não devido a falhas na página. (a menos que seu trabalho conjunto é muito grande para o computador que você está usando.)
Peter Cordes

10

A última vez que me preocupei com isso foi ao escrever a montagem 6502 (8 bits, sim!). O grande ganho é que a maioria das operações aritméticas (principalmente decrementos) atualizou um conjunto de sinalizadores, um deles foi Zo indicador 'atingido zero'.

Então, no final do loop, você apenas executou duas instruções: DEC(decremento) e JNZ(pule se não for zero), nenhuma comparação é necessária!


No caso do JavaScript, obviamente, ele não se aplica (pois é executado em CPUs que não possuem esses códigos operacionais). Provavelmente, a verdadeira razão por trás do i--vs i++é que, no primeiro, você não introduz variáveis ​​de controle extras no escopo do loop. Veja minha resposta abaixo ...
Pavel P:

1
certo, isso realmente não se aplica; mas é um estilo C muito comum e parece mais limpo para aqueles que se acostumaram com isso. :-) #
Javier

x86 e ARM são como 6502 a esse respeito. dec/jnzem vez de inc/cmp/jnepara o caso x86. Você não verá um loop vazio rodando mais rápido (ambos saturam a taxa de transferência de ramificação), mas a contagem regressiva reduz um pouco a sobrecarga do loop. Os pré-buscadores atuais da Intel HW também não se incomodam com os padrões de acesso à memória, que vão em ordem decrescente. Acho que as CPUs mais antigas podem rastrear 4 fluxos para trás e 6 ou 10 para a frente, IIRC.
Peter Cordes

7

Isso pode ser explicado pelo fato de o JavaScript (e todos os idiomas) ser transformado em opcodes para rodar na CPU. As CPUs sempre têm uma única instrução para comparar com zero, o que é muito rápido.

Como um aparte, se você pode garantir que counté sempre >= 0, você pode simplificar:

for (var i = count; i--;)
{
  // whatever
}

1
O código-fonte mais curto não significa necessariamente que será mais rápido. Você mediu isso?
Greg Hewgill 27/08/2009

Opa, eu perdi essa. Prego na cabeça há cap.
Robert

1
Gostaria de ver as fontes de montagem em que comparar com 0 é diferente. Já faz alguns anos, mas uma vez eu fiz uma tonelada de códigos de montagem e não consigo pensar em uma maneira de fazer uma comparação / teste contra 0 de uma maneira que não pode ser tão rápida quanto para qualquer outro número inteiro. No entanto, o que você diz parece verdadeiro. Frustrado que eu não consigo entender o porquê!
27411 Brian Knoblauch

7
Brian Knoblauch: Se você usar uma instrução como "dec eax" (código x86), essa instrução definirá automaticamente o sinalizador Z (zero), que você poderá testar imediatamente sem precisar usar outra instrução de comparação.
Greg Hewgill 27/08/2009

1
Ao interpretar o Javascript, duvido que os opcodes sejam o gargalo. O mais provável é que menos tokens signifiquem que o intérprete possa processar o código-fonte mais rapidamente.
Todd Owen

6

Isso não depende do sinal --ou ++, mas depende das condições que você aplica no loop.

Por exemplo: Seu loop é mais rápido se a variável tiver um valor estático do que se seu loop verificar as condições sempre, como o comprimento de uma matriz ou outras condições.

Mas não se preocupe com essa otimização, porque desta vez seu efeito é medido em nanossegundos.


múltiplos loops nanossegundos pode se tornar segundos ... nunca é uma má idéia para otimizar quando você tem tempo para isso
Buksy

6

for(var i = array.length; i--; )não é muito mais rápido. Mas quando você substitui array.lengthpor super_puper_function(), isso pode ser significativamente mais rápido (já que é chamado em todas as iterações). Essa é a diferença.

Se você vai mudar isso em 2014, não precisa pensar em otimização. Se você deseja alterá-lo com "Pesquisar e substituir", não precisa pensar em otimização. Se você não tiver tempo, não precisará pensar em otimização. Mas agora, você tem tempo para pensar sobre isso.

PS: i--não é mais rápido que i++.


6

Para resumir: Não há absolutamente nenhuma diferença em fazer isso em JavaScript.

Primeiro de tudo, você pode testá-lo:

Não apenas você pode testar e executar qualquer script em qualquer biblioteca JavaScript, mas também ter acesso a todo o conjunto de scripts escritos anteriormente, além da capacidade de ver diferenças entre o tempo de execução em diferentes navegadores em diferentes plataformas.

Portanto, até onde você pode ver, não há diferença entre o desempenho em qualquer ambiente.

Se você deseja melhorar o desempenho do seu script, pode tentar:

  1. Tenha uma var a = array.length;declaração para que você não calcule seu valor todas as vezes no loop
  2. Desenrole o loop http://en.wikipedia.org/wiki/Loop_unwinding

Mas você precisa entender que a melhoria que puder obter será tão insignificante que, na maioria das vezes, você nem se importa.

Minha própria opinião por que esse equívoco (Dez x Inc) apareceu

Há muito, muito tempo atrás, havia uma instrução de máquina comum, DSZ (Decrement and Skip on Zero). As pessoas que programaram na linguagem assembly usaram esta instrução para implementar loops para salvar um registro. Agora, esses fatos antigos estão obsoletos e tenho certeza de que você não obterá nenhuma melhoria de desempenho em nenhum idioma usando essa pseudo melhoria.

Eu acho que a única maneira como esse conhecimento pode se propagar em nosso tempo é quando você lê o código de outra pessoa. Veja essa construção e pergunte por que foi implementada e aqui a resposta: "melhora o desempenho porque se compara a zero". Você ficou desconcertado com o conhecimento superior do seu colega e acha que ele será muito mais inteligente :-)


Interessante, mas para os testes em execução no firefox 16.0.2 no win7, o loop de decréscimo foi 29% mais lento ... EDIT: desconsidere isso. Testes repetidos se mostraram inconclusivos; há uma quantidade surpreendente de ruído nos resultados de meus testes para execuções subseqüentes. Não tenho muita certeza do porquê.

Sim, eu tentei explicar isso fechando tudo o resto e apenas executando os testes. Ainda obteve resultados instáveis. Muito estranho.

Eu acho que você perdeu o ponto real por que ir para zero é considerado melhor em JavaScript. É principalmente porque dessa maneira apenas uma variável controla a execução do loop, por exemplo, dessa maneira, o otimizador / JITer tem mais espaço para melhorias. O uso array.lengthnão implica necessariamente penalidade no desempenho, simplesmente porque a máquina virtual JS é inteligente o suficiente para descobrir se a matriz não é modificada pelo corpo do loop. Veja minha resposta abaixo.
Pavel P

1
nitpicking: esse fato antigo (otimizações da linguagem assembly) não é obsoleto, apenas misterioso. como você não precisa saber, a menos que realmente precise. :-) #
Javier

6

Eu fiz uma comparação no jsbench .

Como alestani apontou, uma coisa que leva tempo em loops ascendentes é avaliar, para cada iteração, o tamanho da sua matriz. Neste loop:

for ( var i = 1; i <= array.length; i++ )

você avalia .lengthcada vez que incrementa i. Neste:

for ( var i = 1, l = array.length; i <= l; i++ )

você avalia .lengthapenas uma vez, quando declara i. Neste:

for ( var i = array.length; i--; )

a comparação está implícita, acontece pouco antes de diminuir ie o código é muito legível. No entanto, o que pode fazer uma grande diferença é o que você coloca dentro do loop.

Loop com chamada à função (definida em outro lugar):

for (i = values.length; i-- ;) {
  add( values[i] );
}

Loop com código embutido:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

Se você puder incorporar seu código, em vez de chamar uma função, sem sacrificar a legibilidade, poderá executar um loop em uma ordem de magnitude mais rápido!


Nota : como o navegador está se tornando bom em incluir funções simples, isso realmente depende da complexidade do seu código. Então, perfil antes de otimizar, porque

  1. O gargalo pode estar em outro lugar (ajax, reflow, ...)
  2. Você pode escolher um algoritmo melhor
  3. Você pode escolher uma melhor estrutura de dados

Mas lembre-se:

O código é escrito para que as pessoas leiam, e apenas acidentalmente para as máquinas executarem.


+1 para esta resposta e para a referência. Adicionei forEach testes e reformulei o benchmark para um arquivo autônomo executável no navegador e no Node. jsfiddle.net/hta69may/2 . Para o Nó "Loop reverso, comparação implícita, código embutido" é o mais rápido. Mas os testes no FF 50 mostraram resultados curiosos: não apenas os tempos foram quase 10 vezes menos (!), Mas os dois testes "forEach" foram tão rápidos quanto o "loop reverso". Talvez os caras do Node devam usar o mecanismo JS do Mozilla em vez do V8? :)
Fr0sT 14/02

4

A maneira como você está fazendo isso agora não é mais rápida (além de ser um loop indefinido, acho que você pretendia fazê-lo i--.

Se você quiser acelerar, faça:

for (i = 10; i--;) {
    //super fast loop
}

é claro que você não notaria isso em um loop tão pequeno. A razão pela qual é mais rápido é porque você está diminuindo i enquanto verifica se é "verdadeiro" (é avaliado como "falso" quando atinge 0)


1
Está faltando um ponto e vírgula? (i = 10; i--;)
searlea

Sim, sim, eu consertei o erro. E se formos exigentes, aponto que você esqueceu seu ponto e vírgula depois do seu i! Heh.
djdd87

Por que isso seria mais rápido? Fonte mais curta não significa que será mais rápida. Você mediu isso?
Greg Hewgill 27/08/2009

4
Sim, eu o medi e não estou dizendo que uma fonte menor a torna mais rápida, mas menos operações a tornam mais rápida.
Peirix 27/08/2009

4

Às vezes, fazer pequenas alterações na maneira como escrevemos nosso código pode fazer uma grande diferença na rapidez com que nosso código é executado. Uma área em que uma pequena alteração no código pode fazer uma grande diferença nos tempos de execução é onde temos um loop for que está processando uma matriz. Onde a matriz é composta por elementos na página da web (como botões de opção), a alteração tem o maior efeito, mas ainda vale a pena aplicá-la, mesmo quando a matriz é interna ao código Javascript.

A maneira convencional de codificar um loop for para processar uma matriz lis como esta:

for (var i = 0; i < myArray.length; i++) {...

O problema disso é que avaliar o comprimento da matriz usando myArray.length leva tempo e a maneira como codificamos o loop significa que essa avaliação deve ser executada toda vez em torno do loop. Se a matriz contiver 1000 elementos, o comprimento da matriz será avaliado 1001 vezes. Se estivéssemos olhando botões de opção e tivéssemos myForm.myButtons.length, a avaliação demoraria ainda mais, pois o grupo apropriado de botões no formulário especificado deve ser localizado primeiro antes que o comprimento possa ser avaliado a cada vez que o loop for avaliado.

Obviamente, não esperamos que o comprimento da matriz mude enquanto o processamos, portanto todos esses recálculos do comprimento estão apenas aumentando desnecessariamente o tempo de processamento. (Obviamente, se você possui um código dentro do loop que adiciona ou remove entradas de matriz, o tamanho da matriz pode mudar entre as iterações e, portanto, não podemos alterar o código que testa isso)

O que podemos fazer para corrigir isso em um loop em que o tamanho é fixo é avaliar o comprimento uma vez no início do loop e salvá-lo em uma variável. Podemos então testar a variável para decidir quando finalizar o loop. Isso é muito mais rápido do que avaliar o comprimento da matriz a cada vez, principalmente quando a matriz contém mais do que apenas algumas entradas ou faz parte da página da web.

O código para fazer isso é:

for (var i = 0, var j = myArray.length; i < j; i++) {...

Portanto, agora avaliamos apenas o tamanho da matriz uma vez e testamos nosso contador de loop contra a variável que mantém esse valor toda vez que o loop é executado. Essa variável extra pode ser acessada muito mais rapidamente do que avaliar o tamanho da matriz e, portanto, nosso código será executado muito mais rápido do que antes. Nós apenas temos uma variável extra em nosso script.

Freqüentemente, não importa em que ordem processamos a matriz desde que todas as entradas na matriz sejam processadas. Onde for esse o caso, podemos tornar nosso código um pouco mais rápido eliminando a variável extra que acabamos de adicionar e processando a matriz em ordem inversa.

O código final que processa nossa matriz da maneira mais eficiente possível é:

for (var i = myArray.length-1; i > -1; i--) {...

Esse código ainda avalia apenas o tamanho da matriz uma vez no início, mas em vez de comparar o contador de loop com uma variável, o comparamos com uma constante. Como uma constante é ainda mais eficaz para acessar do que uma variável e como temos uma declaração de atribuição a menos do que antes, nossa terceira versão do código agora é um pouco mais eficiente que a segunda versão e muito mais eficiente que a primeira.


4

++ vs. -- não importa porque o JavaScript é uma linguagem interpretada, não uma linguagem compilada. Cada instrução se traduz em mais de um idioma de máquina e você não deve se preocupar com os detalhes sangrentos.

As pessoas que estão falando sobre o uso --(ou ++) para fazer uso eficiente das instruções de montagem estão erradas. Essas instruções se aplicam à aritmética inteira e não há números inteiros em JavaScript, apenas números .

Você deve escrever um código legível.


3

Em muitos casos, isso não tem nada a ver com o fato de que os processadores podem comparar a zero mais rapidamente do que outras comparações.

Isso ocorre porque apenas alguns mecanismos Javascript (os da lista JIT) realmente geram código de linguagem de máquina.

A maioria dos mecanismos Javascript constrói uma representação interna do código-fonte que eles interpretam (para ter uma idéia de como é isso, dê uma olhada na parte inferior desta página no SpiderMonkey do Firefox ). Geralmente, se um pedaço de código faz praticamente a mesma coisa, mas leva a uma representação interna mais simples, ele será executado mais rapidamente.

Lembre-se de que, com tarefas simples como adicionar / subtrair uma de uma variável ou comparar uma variável com alguma coisa, a sobrecarga do intérprete que passa de uma "instrução" interna para a próxima é bastante alta; portanto, menos "instruções" são usado internamente pelo mecanismo JS, melhor.


3

Bem, eu não sei sobre JavaScript, deve ser apenas uma questão de reavaliar o tamanho da matriz e talvez algo a ver com as matrizes associativas (se você apenas diminuir, é improvável que novas entradas precisem ser alocadas - se a matriz é densa, ou seja, alguém pode otimizar para isso).

Na montagem de baixo nível, há uma instrução de loop, chamada DJNZ (diminuir e pular se não for zero). Portanto, o decremento e o salto estão em uma única instrução, tornando-o possivelmente um pouco mais rápido que INC e JL / JB (incremento, pule se menor que / pule se abaixo). Além disso, comparar com zero é mais simples do que comparar com outro número. Mas tudo isso é realmente marginal e também depende da arquitetura de destino (pode fazer diferença, por exemplo, no Arm em um smartphone).

Eu não esperaria que essas diferenças de baixo nível tivessem um impacto tão grande nas linguagens interpretadas que simplesmente não vi o DJNZ entre as respostas, então pensei em compartilhar um pensamento interessante.


Para o registro, DJNZé uma instrução no 8051 (z80) ISA. Em dec/jnzvez disso inc/cmp/jne, o x86 possui e aparentemente arm possui algo semelhante. Tenho certeza de que essa não é a causa da diferença de Javascript; isso é ter mais a avaliar na condição de loop.
Peter Cordes

3

É usado para ser dito que --i foi mais rápida (em C ++), porque existe apenas um resultado, o valor decrementado. i-- precisa armazenar o valor decrementado de volta em i e também reter o valor original como resultado (j = i--;). Na maioria dos compiladores, isso usava dois registradores em vez de um que poderia fazer com que outra variável tivesse que ser gravada na memória, em vez de retida como uma variável de registrador.

Concordo com os outros que disseram que não faz diferença nos dias de hoje.


3

Em palavras muito simples

"i-- e i ++. Na verdade, os dois levam o mesmo tempo".

mas neste caso, quando você tem operação incremental .. o processador avalia o comprimento. toda vez que a variável é incrementada em 1 e, em caso de decréscimo. particularmente nesse caso, ele avaliará o comprimento apenas uma vez até obtermos 0.


3

Primeiro, i++e i--use exatamente o mesmo tempo em qualquer linguagem de programação, incluindo JavaScript.

O código a seguir leva um tempo muito diferente.

Rápido:

for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };

Lento:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

Portanto, o código a seguir também leva um tempo diferente.

Rápido:

for (var i = Things.length - 1; i >= 0; i--) { Things[i] };

Lento:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

O PS Slow é lento apenas para alguns idiomas (mecanismos JavaScript) devido à otimização do compilador. A melhor maneira é usar '<' em vez '<=' (ou '=') e '--i' em vez de 'i--' .


3

Não é consumido muito tempo pelo i-- ou pelo i ++. Se você vai lá no fundo da arquitetura de CPU o ++é mais rápida do que a --, uma vez que a --operação vai fazer o complemento de 2 de, mas aconteceu dentro do hardware para que isso irá torná-lo rápido e há grande diferença entre o ++e --também essas operações são consideradas de menos tempo consumido na CPU.

O loop for roda assim:

  • Inicialize a variável uma vez no início.
  • Verifique a restrição no segundo operando do circuito, <, >, <=, etc.
  • Em seguida, aplique o loop.
  • Incremente o loop e loop novamente, jogue esses processos novamente.

Assim,

for (var i = Things.length - 1; i >= 0; i--) {
    Things[i]
}; 

calculará o comprimento da matriz apenas uma vez no início e isso não é muito tempo, mas

for(var i = array.length; i--; ) 

calculará o comprimento em cada loop, portanto consumirá muito tempo.


var i = Things.length - 1; i >= 0; i--irá calcular o comprimento 1 vez também.
Dmitry

1
Não sei o que significa " --operação fará o complemento do 2", mas presumo que isso signifique que ele negará algo. Não, isso não nega nada em nenhuma arquitetura. Subtrair 1 é tão simples quanto adicionar 1, você apenas faz um circuito que pede emprestado ao invés de transportar.
21413 Olathe

1

A melhor abordagem para responder a esse tipo de pergunta é realmente tentar. Configure um loop que conte um milhão de iterações ou o que for, e faça nos dois sentidos. Cronometre os dois loops e compare os resultados.

A resposta provavelmente dependerá de qual navegador você está usando. Alguns terão resultados diferentes dos outros.


4
Isso ainda não responderia à sua pergunta sobre por que é mais rápido. Ele tinha acabado de ter uma referência, sem qualquer conhecimento de por que é assim ...
peirix

3
Isso é verdade. No entanto, sem saber a implementação exata de cada mecanismo Javascript em cada navegador, é quase impossível responder a parte "por que". Muitas respostas aqui trazem recomendações anedóticas como "usar predileção em vez de pós-adulteração" (um truque de otimização de C ++) e "comparar com zero", o que pode ser verdadeiro nas linguagens compiladas por código nativo, mas o Javascript está bem longe do metal básico do CPU.
Greg Hewgill 27/08/2009

4
-1 Discordo completamente de que a melhor abordagem é experimentá-lo. Alguns exemplos não substituem o conhecimento coletivo, e esse é o objetivo de fóruns como este.
Christophe

1

Adoro, muitas marcas, mas não há resposta: D

Basta colocar uma comparação contra zero é sempre a comparação mais rápida

Então (a == 0) é realmente mais rápido ao retornar True do que (a == 5)

É pequeno e insignificante e com 100 milhões de linhas em uma coleção é mensurável.

ou seja, em um loop, você pode estar dizendo onde i <= array.length e incrementando i

em um loop descendente, você pode estar dizendo onde i> = 0 e decrementando i.

A comparação é mais rápida. Não é a 'direção' do loop.


Não há resposta para a pergunta, conforme declarado, porque os mecanismos Javascript são todos diferentes e a resposta depende exatamente de qual navegador você está medindo.
Greg Hewgill 27/08/2009

Não, as comparações fundamentalmente aceitas contra zero são as mais rápidas. Embora suas declarações também estejam corretas, a regra de ouro da comparação com zero é absoluta.
Robert

4
Isso é verdade apenas se o compilador optar por fazer essa otimização, o que certamente não é garantido. O compilador pode gerar exatamente o mesmo código para (a == 0) e (a == 5), exceto pelo valor da constante. Uma CPU não se compara a 0 mais rapidamente do que a qualquer outro valor, se os dois lados da comparação forem variáveis ​​(do ponto de vista da CPU). Geralmente, apenas os compiladores de código nativo têm a oportunidade de otimizar nesse nível.
Greg Hewgill 27/08/2009

1

AJUDAR OUTROS EVITAR UMA DOR DE CABEÇA --- VOTE ISTO !!!

A resposta mais popular nesta página não funciona no Firefox 14 e não passa no jsLinter. Os loops "while" precisam de um operador de comparação, não de uma atribuição. Ele funciona no chrome, safari e até mesmo no ie. Mas morre no firefox.

ISTO ESTÁ QUEBRADO!

var i = arr.length; //or 10
while(i--)
{
  //...
}

ISSO FUNCIONARÁ! (funciona no firefox, passa o jsLinter)

var i = arr.length; //or 10
while(i>-1)
{
  //...
  i = i - 1;
}

2
Eu apenas tentei no Firefox 14 e funcionou bem. Eu já vi exemplos de bibliotecas de produção que usam while (i--)e que funcionam em muitos navegadores. Poderia ter havido algo estranho no seu teste? Você estava usando uma versão beta do Firefox 14?
Matt Browne

1

Isso é apenas um palpite, mas talvez seja porque é mais fácil para o processador comparar algo com 0 (i> = 0) em vez de com outro valor (i <Things.length).


3
+1 Outros não votaram. Embora a avaliação repetida do comprimento seja muito mais um problema do que o aumento / redução real, a verificação do final do loop pode ser importante. Meu compilador C dá alguns avisos sobre loops como: "observação # 1544-D: (ULP 13.1) ciclo detectado contando Recomendar loops de contagem regressiva, como a detecção de zeros é mais fácil."
harper
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.