Existe uma diferença de desempenho entre i ++ e ++ i em C?

Existe uma diferença de desempenho entre i++ e ++i se o valor resultante não for usado?

Resumo executivo: Não.

i++ poderia ser mais lento que ++i , já que o antigo valor de i talvez precise ser salvo para uso posterior, mas, na prática, todos os compiladores modernos irão otimizá-lo.

Podemos demonstrar isso observando o código dessa function, tanto com o ++i quanto com o i++ .

 $ cat i++.c extern void g(int i); void f() { int i; for (i = 0; i < 100; i++) g(i); } 

Os arquivos são os mesmos, exceto para ++i e i++ :

 $ diff i++.c ++ic 6c6 < for (i = 0; i < 100; i++) --- > for (i = 0; i < 100; ++i) 

Vamos compilá-los e também obter o assembler gerado:

 $ gcc -c i++.c ++ic $ gcc -S i++.c ++ic 

E podemos ver que o object gerado e os arquivos do assembler são os mesmos.

 $ md5 i++.s ++is MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e MD5 (++is) = 90f620dda862cd0205cd5db1f2c8c06e $ md5 *.o MD5 (++io) = dd3ef1408d3a9e4287facccec53f7d22 MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22 

De eficiência versus intenção por Andrew Koenig:

Primeiro, está longe de ser óbvio que ++i é mais eficiente que i++ , pelo menos onde variables ​​inteiras estão envolvidas.

E:

Portanto, a pergunta que se deve fazer é não qual dessas duas operações é mais rápida, é qual dessas duas operações expressa com mais precisão o que você está tentando realizar. Eu submeto que se você não está usando o valor da expressão, nunca há uma razão para usar o i++ vez do ++i , porque nunca há uma razão para copiar o valor de uma variável, incrementar a variável e depois lançar o valor copie.

Então, se o valor resultante não for usado, eu usaria ++i . Mas não porque é mais eficiente: porque afirma corretamente minha intenção.

Uma resposta melhor é que ++i às vezes serei mais rápido, mas nunca mais lento.

Todo mundo parece estar assumindo que i é um tipo embutido regular, como int . Neste caso, não haverá diferença mensurável.

No entanto, se i for do tipo complexo, então você pode encontrar uma diferença mensurável. Para o i++ você deve fazer uma cópia da sua class antes de incrementá-la. Dependendo do que está envolvido em uma cópia, pode ser mais lento, pois com ++it você pode retornar o valor final.

 Foo Foo::operator++() { Foo oldFoo = *this; // copy existing value - could be slow // yadda yadda, do increment return oldFoo; } 

Outra diferença é que com ++i você tem a opção de retornar uma referência em vez de um valor. Novamente, dependendo do que está envolvido em fazer uma cópia do seu object, isso pode ser mais lento.

Um exemplo do mundo real de onde isso pode ocorrer seria o uso de iteradores. É improvável que copiar um iterador seja um gargalo em seu aplicativo, mas ainda é uma boa prática adquirir o hábito de usar ++i vez de i++ onde o resultado não é afetado.

Aqui está uma observação adicional se você estiver preocupado com a otimização micro. Loops decrescentes podem ‘possivelmente’ ser mais eficientes do que incrementar loops (dependendo da arquitetura do conjunto de instruções, por exemplo, ARM), dado:

 for (i = 0; i < 100; i++) 

Em cada loop você terá uma instrução cada para:

  1. Adicionando 1 a i .
  2. Compare se i é menor que 100 .
  3. Um ramo condicional se i for menor que 100 .

Considerando um loop decrescente:

 for (i = 100; i != 0; i--) 

O loop terá uma instrução para cada um dos seguintes:

  1. Decrementar i , configurando o sinalizador de status do registrador da CPU.
  2. Um ramo condicional dependendo do status do registro da CPU ( Z==0 ).

Claro que isso funciona apenas quando decrementar a zero!

Lembrado do Guia do desenvolvedor do sistema ARM.

Tomando uma folha de Scott Meyers, Mais Eficaz c ++ Item 6: Distinguir entre as formas de prefixo e postfix das operações de incremento e decremento .

A versão do prefixo é sempre preferível ao postfix em relação aos objects, especialmente no que diz respeito aos iteradores.

A razão para isso, se você olhar para o padrão de chamadas dos operadores.

 // Prefix Integer& Integer::operator++() { *this += 1; return *this; } // Postfix const Integer Integer::operator++(int) { Integer oldValue = *this; ++(*this); return oldValue; } 

Olhando para este exemplo, é fácil ver como o operador prefix sempre será mais eficiente que o postfix. Por causa da necessidade de um object temporário no uso do postfix.

É por isso que quando você vê exemplos usando iteradores, eles sempre usam a versão do prefixo.

Mas, como você aponta para int, não há diferença efetiva por causa da otimização do compilador que pode ocorrer.

Resposta curta:

Nunca há qualquer diferença entre i++ e ++i em termos de velocidade. Um bom compilador não deve gerar código diferente nos dois casos.

Resposta longa:

O que todas as outras respostas não mencionam é que a diferença entre ++i versus i++ só faz sentido dentro da expressão que é encontrada.

No caso de for(i=0; i , o i++ está sozinho em sua própria expressão: existe um ponto de sequência antes do i++ e há um após ele. Assim, o único código de máquina gerado é "aumentar i por 1 " e está bem definido como isso é sequenciado em relação ao resto do programa. Então, se você mudasse para prefixo ++ , não importaria nem um pouco, você ainda teria o código de máquina "aumentar i por 1 ".

As diferenças entre ++i e i++ só importam em expressões como array[i++] = x; versus array[++i] = x; . Alguns podem argumentar e dizer que o postfix será mais lento em tais operações, porque o registro onde i reside precisa ser recarregado mais tarde. Mas então note que o compilador está livre para ordenar suas instruções da maneira que lhe agrada, desde que não "quebre o comportamento da máquina abstrata" como o padrão C a chama.

Então, enquanto você pode assumir que array[i++] = x; é traduzido para código de máquina como:

  • Armazene o valor de i no registro A.
  • Armazene o endereço da matriz no registro B.
  • Adicione A e B, armazene os resultados em A.
  • Nesse novo endereço representado por A, armazene o valor de x.
  • Armazene o valor de i no registrador A // ineficiente porque a instrução extra aqui, nós já fizemos isso uma vez.
  • Registrador de incremento A.
  • Armazene o registrador A em i .

o compilador pode também produzir o código com mais eficiência, como:

  • Armazene o valor de i no registro A.
  • Armazene o endereço da matriz no registro B.
  • Adicione A e B, armazene os resultados em B.
  • Registrador de incremento A.
  • Armazene o registrador A em i .
  • ... // resto do código.

Só porque você como um programador C é treinado para pensar que o postfix ++ acontece no final, o código da máquina não precisa ser ordenado dessa forma.

Portanto, não há diferença entre prefixo e postfix ++ em C. Agora, o que você como um programador C deve ser variar, é pessoas que usam inconsistentemente o prefixo em alguns casos e postfix em outros casos, sem qualquer razão por que. Isso sugere que eles não estão certos sobre como o C funciona ou que possuem um conhecimento incorreto do idioma. Isso é sempre um mau sinal, por sua vez, sugere que eles estão tomando outras decisões questionáveis ​​em seu programa, baseados em superstições ou "dogmas religiosos".

"Prefix ++ é sempre mais rápido" é de fato um desses dogmas falsos que é comum entre os futuros programadores C.

Por favor, não deixe a questão de “qual é mais rápido” ser o fator decisivo de qual usar. As chances são de que você nunca vai se importar tanto, e além disso, o tempo de leitura do programador é muito mais caro do que o tempo da máquina.

Use o que mais fizer sentido para o humano ler o código.

Primeiro de tudo: A diferença entre i++ e ++i é neglegível em C.


Para os detalhes.

1. O problema bem conhecido do C ++: ++i é mais rápido

Em C ++, ++i é mais eficiente iff i é algum tipo de object com um operador de incremento sobrecarregado.

Por quê?
Em ++i , o object é primeiro incrementado e pode ser passado subseqüentemente como uma referência const para qualquer outra function. Isto não é possível se a expressão for foo(i++) porque agora o incremento precisa ser feito antes que foo() seja chamado, mas o valor antigo precisa ser passado para foo() . Conseqüentemente, o compilador é forçado a fazer uma cópia de i antes de executar o operador de incremento no original. As chamadas adicionais de construtor / destrutor são a parte ruim.

Como observado acima, isso não se aplica aos tipos fundamentais.

2. O fato pouco conhecido: i++ pode ser mais rápido

Se nenhum construtor / destrutor precisar ser chamado, o que é sempre o caso em C, ++i e i++ devem ser igualmente rápidos, certo? Não. Eles são virtualmente igualmente rápidos, mas pode haver pequenas diferenças, que a maioria dos outros respondentes deu errado.

Como posso i++ ser mais rápido?
O ponto é dependencies de dados. Se o valor precisar ser carregado da memory, duas operações subseqüentes precisarão ser feitas com ele, incrementando-o e usando-o. Com ++i , o incremento precisa ser feito antes que o valor possa ser usado. Com o i++ , o uso não depende do incremento e a CPU pode executar a operação de uso em paralelo à operação de incremento. A diferença é no máximo um ciclo de CPU, então é realmente neglegível, mas está lá. E é o inverso que muitos esperariam.

@Mark Mesmo que o compilador tenha permissão para otimizar a cópia temporária (baseada em pilha) da variável e gcc (em versões recentes) está fazendo isso, não significa que todos os compiladores sempre farão isso.

Eu apenas testei com os compiladores que usamos em nosso projeto atual e 3 de 4 não o otimizaram.

Nunca assuma que o compilador acerta, especialmente se o código possivelmente mais rápido, mas nunca mais lento, é tão fácil de ler.

Se você não tiver uma implementação realmente estúpida de um dos operadores em seu código:

Alwas preferem ++ i sobre i ++.

Em C, o compilador geralmente pode otimizá-los para serem os mesmos se o resultado não for utilizado.

No entanto, em C ++, se estiver usando outros tipos que forneçam seus próprios operadores ++, a versão do prefixo provavelmente será mais rápida que a versão do postfix. Então, se você não precisa da semântica do postfix, é melhor usar o operador prefix.

Eu posso pensar em uma situação em que o postfix é mais lento que o incremento de prefixo:

Imagine que um processador com registrador A seja usado como acumulador e seja o único registro usado em muitas instruções (alguns microcontroladores pequenos são realmente assim).

Agora imagine o seguinte programa e sua tradução em uma assembly hipotética:

Incremento de prefixo:

 a = ++b + c; ; increment b LD A, [&b] INC A ST A, [&b] ; add with c ADD A, [&c] ; store in a ST A, [&a] 

Incremento de postfix:

 a = b++ + c; ; load b LD A, [&b] ; add with c ADD A, [&c] ; store in a ST A, [&a] ; increment b LD A, [&b] INC A ST A, [&b] 

Observe como o valor de b foi forçado a ser recarregado. Com o incremento de prefixo, o compilador pode apenas incrementar o valor e continuar usando-o, possivelmente evitando recarregá-lo, já que o valor desejado já está no registrador após o incremento. No entanto, com o incremento de postfix, o compilador tem que lidar com dois valores, um o antigo e um o valor incrementado que, como mostrado acima, resulta em mais um access à memory.

Claro, se o valor do incremento não for usado, como um único i++; declaração, o compilador pode (e) simplesmente gerar uma instrução de incremento independentemente do uso do prefixo ou do prefixo.


Como uma nota secundária, gostaria de mencionar que uma expressão na qual há um b++ não pode simplesmente ser convertida em uma com ++b sem qualquer esforço adicional (por exemplo, adicionando a - 1 ). Então, comparar os dois se eles fazem parte de alguma expressão não é realmente válido. Muitas vezes, quando você usa b++ dentro de uma expressão, você não pode usar ++b , então, mesmo que ++b fosse potencialmente mais eficiente, seria simplesmente errado. A exceção é, naturalmente, se a expressão está implorando por ela (por exemplo, a = b++ + 1; que pode ser alterada para a = ++b; ).

Eu sempre prefiro pré-incremento, no entanto …

Eu queria ressaltar que mesmo no caso de chamar a function operator ++, o compilador será capaz de otimizar o temporário se a function for embutida. Como o operador ++ geralmente é curto e geralmente implementado no header, é provável que ele seja inlined.

Portanto, para fins práticos, provavelmente não há muita diferença entre o desempenho dos dois formulários. No entanto, sempre prefiro o pré-incremento, pois parece melhor expressar diretamente o que estou tentando dizer, em vez de confiar no otimizador para descobrir isso.

Além disso, deixar o otimizador menos provável significa que o compilador é executado mais rapidamente.

Meu C está um pouco enferrujado, por isso peço desculpas antecipadamente. Speedwise, eu posso entender os resultados. Mas, estou confuso sobre como ambos os arquivos saíram para o mesmo hash MD5. Talvez um loop for execute o mesmo, mas as duas linhas de código a seguir não gerariam assembly diferente?

 myArray[i++] = "hello"; 

vs

 myArray[++i] = "hello"; 

O primeiro escreve o valor no array e, em seguida, incrementa i. O segundo incrementa então escrevo para o array. Não sou especialista em assembly, mas não vejo como o mesmo executável seria gerado por essas duas linhas de código diferentes.

Apenas meus dois centavos.