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

Eu já ouvi isso algumas vezes. Os loops JavaScript são realmente mais rápidos ao contar para trás? Se sim, porque? Eu vi alguns exemplos de suíte de teste mostrando que os loops invertidos são mais rápidos, mas não consigo encontrar nenhuma explicação do porquê!

Estou assumindo que é porque o loop não precisa mais avaliar uma propriedade toda vez que ele verifica se está terminado 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. } 

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

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

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

Você avalia o .length apenas uma vez, quando declara i , enquanto para esse loop

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

você avalia .length cada vez que você incrementa i , quando você verifica se i < = array.length .

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

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 perdi um na minha leitura) o loop mais rápido foi:

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

Eu tento dar uma visão ampla com essa resposta.

Os seguintes pensamentos entre parênteses foram minha crença até que eu testei recentemente 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 é zero (ou diferente de zero).
Além disso, se você se preocupa com essa otimização, você pode ir em vez de i++ , porque ++i é um comando de processador único, enquanto i++ significa j=i+1, i=j .]]

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

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

Pode ser bem 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 , você pode otimizar as coisas se depender de bibliotecas, funções internas for looping. Deixe-os decidir como é feito melhor.

Conseqüentemente, em JavaScript, sugiro usar algo como

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

Também é menos propenso a erros e os navegadores têm a chance de otimizar seu código.

[OBSERVAÇÃO: não apenas os navegadores, mas você também tem um espaço para otimizar facilmente, apenas redefina a function forEach (navegador dependente) para que ele use as últimas melhores truques! 🙂 @AMK diz que em casos especiais vale a pena usar array.pop ou array.shift . Se você fizer isso, coloque-a atrás da cortina. O maior exagero é adicionar opções a forEach para selecionar o algoritmo de loop.]

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

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

Eu fiz um pouco mais de escrutínio e descobriu-se que em C / C ++, mesmo para operações de 5e9 = (50.000×100.000), não há diferença entre subir e descer se o teste é feito contra uma constante como diz @alestanis. (Os resultados do JsPerf às vezes são inconsistentes, mas em geral dizem o mesmo: você não pode fazer uma grande diferença.)
Então, acontece de ser uma coisa “elegante”. Isso só faz você parecer um programador melhor. 🙂

Por outro lado, para o desenrolar desta situação 5e9, isso me fez descer de 12 segundos para 2,5 segundos quando eu passei por 10s, e para 2,1 segundos quando eu passei por 20s. Não houve otimização, e a otimização reduziu as coisas a pouco tempo imensurável. 🙂 (Desenrolar pode ser feito no meu caminho acima ou usando o i++ , mas isso não traz as coisas adiante em JavaScript.)

Tudo em tudo: mantenha i++ / i++ e ++i / i++ diferenças para as entrevistas de emprego, furar a array.forEach ou outras funções de biblioteca complexas, quando disponíveis. 😉

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 grandes matrizes pode-se ver a degradação do desempenho.

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

Eu construí um conjunto de teste de benchmarking de loop para diferentes formas de codificação de loops em JavaScript. Já existem alguns desses, mas não encontrei nenhum que reconhecesse a diferença entre os arrays nativos e as collections de 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:

Um benchmark mais recente criado por Milan Adamovsky pode ser executado em tempo de execução aqui para diferentes navegadores.

Para um teste no Firefox 17.0 no Mac OS X 10.6 , obtive 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.

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 disponível, enquanto comparar com um inteiro finito requer uma subtração.

a++ < length

é realmente compilado como

 a++ test (a-length) 

Portanto, leva mais tempo no processador quando compilado.

Resposta curta

Para o 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 loop for e na instrução compare .

Isso se aplica a todos os idiomas de alto nível e é independente do uso de JavaScript. A explicação é o código 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 de código do montador você pode ver que uma compare with 0 é apenas uma instrução que não precisa de um registro adicional.

Essa comparação é emitida em todas as etapas do loop e pode resultar em uma melhoria de desempenho mensurável.

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

será avaliado para um pseudocódigo como este:

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

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

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

será avaliado para um pseudocódigo como este (otimização de interpretador normal suposta):

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

Note que end é uma variável que precisa de um registrador de CPU . Isso pode invocar uma troca de registro adicional no código e precisa de uma instrução de comparação mais cara na instrução if .

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 da matriz do início ao fim é melhor.

A iteração mais rápida da extremidade da matriz para iniciar resulta na seqüência reversa possivelmente indesejada.

Post Scriptum

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

A melhor explicação é experimentá-lo 😉 Aqui está um exemplo de Bash .

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

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 bem conhecida e particularmente eficiente em JavaScript quando a matriz faz parte do documento HTML (fazendo um for todos os elementos li ).

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 estou, a principal melhoria na forma em sua pergunta é o fato de que ela não declara uma variável extra ( len no 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 ).

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

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

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

Provavelmente, você pensou que um era mais do que o outro 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ê escreve

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

depois, em cada array.length iteração, o array.length tinha que ser avaliado (um mecanismo de JavaScript mais inteligente talvez pudesse descobrir que o loop não mudaria o tamanho do array). Mesmo que pareça uma simples declaração, na verdade é uma function que é chamada sob o capô pelo mecanismo JavaScript.

A outra razão, porque i-- poderia ser considerado "mais rápido" é porque o mecanismo de JavaScript precisa alocar apenas uma variável interna para controlar o loop (variável para o var i ). Se você comparar com array.length ou com alguma outra variável, deve haver mais de uma variável interna para controlar o loop, e o número de variables ​​internas é um ativo limitado de um mecanismo JavaScript. Quanto menos variables ​​forem usadas em um loop, mais chance o JIT tem para otimização. É por isso que i-- poderia ser considerado mais rápido

Já que nenhuma das outras respostas parecem responder à sua pergunta específica (mais da metade delas mostram exemplos C e discutem linguagens de nível mais baixo, sua pergunta é para JavaScript) Eu decidi escrever a minha própria.

Então, aqui vai você:

Resposta simples: geralmente é mais rápido porque não precisa executar uma comparação a 0 toda vez que é executado, os resultados dos testes em vários methods estão abaixo:

Resultados do teste: Como “comprovado” por este jsPerf, arr.pop() é realmente o loop mais rápido de longe. Mas, focando em --i , --i , i++ e ++i como você pediu em sua pergunta, aqui estão jsPerf (eles são de vários jsPerf’s, por favor, veja fonts abaixo) resultados resumidos:

--i e --i são os mesmos no Firefox enquanto i-- é mais rápido no Chrome.

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

No Google Chrome e no Firefox, um arr.length armazenamento em cache é significativamente mais rápido, com o Chrome adiantado em cerca de 170.000 operações / segundo.

Sem uma diferença significativa, ++i é mais rápido que i++ na maioria dos navegadores, AFAIK, nunca é o contrário em qualquer 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.

Depende da colocação da sua matriz na memory e da taxa de acertos das páginas de memory enquanto você está acessando essa matriz.

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

A última vez que me preocupei com isso foi quando escrevi 6502 assembly (8 bits, sim!). O grande ganho é que a maioria das operações aritméticas (especialmente decrementos) atualizou um conjunto de sinalizadores, um deles era Z , o indicador ‘alcançado zero’.

Então, no final do loop você acabou de fazer duas instruções: DEC (decrementar) e JNZ (pular se não for zero), nenhuma comparação é necessária!

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

Primeiro de tudo, você pode testar você mesmo:

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

Então, tanto quanto você pode ver, não há diferença entre o desempenho em qualquer ambiente.

Se você quiser melhorar o desempenho do seu script, pode tentar fazer o seguinte:

  1. Tenha um var a = array.length; declaração de modo que você não estará calculando seu valor de cada vez no loop
  2. Fazer loop desenrolando http://en.wikipedia.org/wiki/Loop_unwinding

Mas você tem que entender que a melhora que você pode ganhar será tão insignificante, que você nem deveria se importar com isso.

Minha própria opinião porque tal equívoco (Dec vs Inc) apareceu

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

Eu acho que a única maneira de tal conhecimento se propagar em nosso tempo é quando você lê o código de outra pessoa. Veja tal construção e pergunte por que foi implementada e aqui a resposta: “melhora o desempenho porque se compara a zero”. Você ficou confuso com o conhecimento mais alto do seu colega e pensa em usá-lo para ser muito mais inteligente 🙂

Isso pode ser explicado pelo JavaScript (e todas as linguagens) sendo transformado em opcodes para rodar na CPU. CPUs sempre tem uma única instrução para comparar com zero, o que é extremamente rápido.

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

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

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

Por exemplo: Seu loop é mais rápido se a variável tiver um valor estático do que se as condições de verificação de loop sempre ocorrerem, 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.

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

Se você pretende mudá-lo em 2014, não precisa pensar em otimização. Se você pretende alterá-lo com “Search & Replace”, não precisa pensar em otimização. Se você não tem tempo, não precisa pensar em otimização. Mas agora você tem tempo para pensar sobre isso.

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

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

A maneira convencional de codificar um loop for para processar uma matriz é assim:

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

O problema com isto é que avaliar o comprimento da matriz usando myArray.length leva tempo e a maneira que nós codificamos o loop significa que esta avaliação tem que ser executada toda vez ao redor do loop. Se a matriz contiver 1000 elementos, o comprimento da matriz será avaliado 1001 vezes. Se estivéssemos olhando para botões de rádio e tivéssemos myForm.myButtons.length, levaria ainda mais tempo para avaliar, já que o grupo apropriado de botões dentro do formulário especificado deve primeiro ser localizado antes que o comprimento possa ser avaliado a cada volta do loop.

Obviamente, não esperamos que o comprimento do array mude enquanto o processamos, então todos esses recálculos do comprimento estão apenas adicionando desnecessariamente ao tempo de processamento. (Of course if you have code inside the loop that adds or removes array entries then the array size can change between iterations and so we can't change the code that tests for it)

What we can do to correct this for a loop where the size is fixed is to evaluate the length once at the start of the loop and save it in a variable. We can then test the variable to decide when to terminate the loop. This is much faster than evaluating the array length each time particularly when the array contains more than just a few entries or is part of the web page.

The code to do this is:

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

So now we only evaluate the size of the array once and test our loop counter against the variable that holds that value each time around the loop. This extra variable can be accessed much faster than evaluating the size of the array and so our code will run much faster than before. We just have one extra variable in our script.

Often it doesn't matter what order we process the array in as long as all of the entries in the array get processed. Where this is the case we can make our code slightly faster by doing away with the extra variable that we just added and processing the array in reverse order.

The final code that processes our array in the most efficient way possible is:

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

This code still only evaluates the size of the array once at the start but instead of comparing the loop counter with a variable we compare it with a constant. Since a constant is even more effective to access than a variable and since we have one fewer assignment statement than before our third version of the code is now slightly more efficient than the second version and vastly more efficient than the first.

I made a comparison on jsbench .

As alestani pointed out, one thing that takes time in ascending loops, is to evaluate, for each iteration, the size of your array. In this loop:

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

you evaluate .length each time you increment i . In this one:

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

you evaluate .length only once, when you declare i . In this one:

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

the comparison is implicit, it happens just before decrementing i , and the code is very readable. However, what can make a terrific difference, is what you put inside the loop.

Loop with call to function (defined elsewhere):

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

Loop with inlined code:

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

If you can inline your code, instead of calling a function, without sacrificing legibility, you can have a loop an order of magnitude faster!


Note : as browser are becoming good at inlining simple functions, it really depends on how complex your code is. So, profile before optimizing, because

  1. The bottleneck may be elsewhere (ajax, reflow, ...)
  2. You may choose a better algorithm
  3. You may choose a better data structure

But remember:

Code is written for people to read, and only incidentally for machines to execute.

The way you’re doing it now isn’t faster (apart from it being an indefinite loop, I guess you meant to do i-- .

If you want to make it faster do:

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

of course you wouldn’t notice it on such a small loop. The reason it’s faster is because you’re decrementing i while checking that it’s “true” (it evaluates to “false” when it reaches 0)

In many cases, this has essentially nothing to do with the fact that processors can compare to zero faster than other comparisons.

This is because only a few Javascript engines (the ones in the JIT list) actually generate machine language code.

Most Javascript engines build an internal representation of the source code which they then interpret (to get an idea of what this is like, have a look near the bottom of this page on Firefox’s SpiderMonkey ). Generally if a piece of code does practically the same thing but leads to a simpler internal representation, it will run faster.

Bear in mind that with simple tasks like adding/subtracting one from a variable, or comparing a variable to something, the overhead of the interpreter moving from one internal “instruction” to the next is quite high, so the less “instructions” that are used internally by the JS engine, the better.

Well, I don’t know about JavaScript, it should really be just a matter of re-evaluation array length and maybe something to do with the associative arrays (if you only decrement, it is unlikely new entries would need to be allocated – if the array is dense, that is. someone may optimize for that).

In low-level assembly, there is a looping instruction, called DJNZ (decrement and jump if non-zero). So the decrement and jump is all in one instruction, making it possibly ever-so-slightly faster than INC and JL / JB (increment, jump if less than / jump if below). Also, comparing against zero is simpler than comparing against another number. But all that is really marginal and also depends on target architecture (could make difference eg on Arm in a smartphone).

I wouldn’t expect this low-level differences to have so great impact on interpreted languages, I just haven’t seen DJNZ among the responses so I thought I would share an interesting thought.

It used to be said that –i was faster (in C++) because there is only one result, the decremented value. i– needs to store the decremented value back to i and also retain the original value as the result (j = i–;). In most compilers this used up two registers rather than one which could cause another variable to have to be written to memory rather than retained as a register variable.

I agree with those others that have said it makes no difference these days.

In very simple words

“i– and i++. Actually, they’re both takes the same time”.

but in this case when you have incremental operation.. processor evaluate the .length every time variable is incremented by 1 and in case of decrement.. particularly in this case, it will evaluate .length only once till we get 0.

++ vs. -- does not matter because JavaScript is an interpreted language, not a compiled language. Each instruction translates to more than one machine language and you should not care about the gory details.

People who are talking about using -- (or ++ ) to make efficient use of assembly instructions are wrong. These instruction apply to integer arithmetic and there are no integers in JavaScript, just numbers .

You should write readable code.

First, i++ and i-- take exactly the same time on any programming language, including JavaScript.

The following code take much different time.

Fast:

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

Slow:

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

Therefore the following code take different time too.

Fast:

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

Slow:

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

PS Slow is slow only for a few languages (JavaScript engines) because of compiler's optimization. The best way is to use '< ' instead '<=' (or '=') and '--i' instead 'i--' .

Not a lot of time is consumed by i– or i++. If you go deep inside the CPU architecture the ++ is more speedy than the -- , since the -- operation will do the 2’s complement, but it happend inside the hardware so this will make it speedy and no major difference between the ++ and -- also these operations are considered of the least time consumed in the CPU.

The for loop runs like this:

  • Initialize the variable once at the start.
  • Check the constraint in the second operand of the loop, < , > , < = , etc.
  • Then apply the loop.
  • Increment the loop and loop again throw these processes again.

Assim,

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

will calculate the array length only once at the start and this is not a lot of time, but

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

will calculate the length at each loop, so it will consume a lot of time.

The best approach to answering this sort of question is to actually try it. Set up a loop that counts a million iterations or whatever, and do it both ways. Time both loops, and compare the results.

The answer will probably depend on which browser you are using. Some will have different results than others.

Love it, lots of marks up but no answer 😀

Simply put a comparison against zero is always the fastest comparison

So (a==0) is actually quicker at returning True than (a==5)

It’s small and insignificant and with 100 million rows in a collection it’s measurable.

ie on a loop up you might be saying where i < = array.length and be incrementing i

on a down loop you might be saying where i >= 0 and be decrementing i instead.

The comparison is quicker. Not the ‘direction’ of the loop.

HELP OTHERS AVOID A HEADACHE — VOTE THIS UP!!!

The most popular answer on this page does not work for Firefox 14 and does not pass the jsLinter. “while” loops need a comparison operator, not an assignment. It does work on chrome, safari, and even ie. But dies in firefox.

THIS IS BROKEN!

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

THIS WILL WORK! (works on firefox, passes the jsLinter)

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

This is just a guess, but maybe it’s because it’s easier for the processor to compare something with 0 ( i >= 0 ) instead of with another value ( i < Things.length).