Existe uma diferença de desempenho entre i++
e ++i
se o valor resultante não for usado?
Existe uma diferença de desempenho entre i++
e ++i
se o valor resultante não for usado?
Respostas:
Resumo executivo: Não.
i++
pode ser potencialmente mais lento do que ++i
, já que o valor antigo de i
talvez precise ser salvo para uso posterior, mas na prática todos os compiladores modernos otimizarão isso.
Podemos demonstrar isso observando o código dessa função, com ++i
e 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 ++i
e i++
:
$ diff i++.c ++i.c
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 ++i.c
$ gcc -S i++.c ++i.c
E podemos ver que o objeto gerado e os arquivos do assembler são os mesmos.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
vez de i++
. Não há absolutamente nenhuma razão para não fazê-lo, e se seu software passar por uma cadeia de ferramentas que não a otimiza, seu software será mais eficiente. Considerando que é tão fácil digitar ++i
quanto digitar i++
, não há realmente desculpa para não usar ++i
em primeiro lugar.
De eficiência versus intenção de Andrew Koenig:
Primeiro, está longe de ser óbvio que
++i
é mais eficiente quei++
, pelo menos no que diz respeito a variáveis inteiras.
E:
Portanto, a pergunta que se deve fazer não é qual dessas duas operações é mais rápida, mas qual dessas duas operações expressa com mais precisão o que você está tentando realizar. Sugiro que se você não estiver usando o valor da expressão, nunca há uma razão para usar
i++
em vez de++i
, porque nunca há uma razão para copiar o valor de uma variável, incrementar a variável, e depois jogar a cópia de distância.
Portanto, se o valor resultante não for usado, eu usaria ++i
. Mas não porque é mais eficiente: porque afirma corretamente minha intenção.
i++
da mesma maneira que codificaria i += n
ou i = i + n
, no formato objeto verbo alvo , com o operando alvo à esquerda do operador verbo . No caso de , não há objeto certo , mas a regra ainda se aplica, mantendo o destino à esquerda do operador do verbo . i++
Uma resposta melhor é que ++i
às vezes será mais rápido, mas nunca mais lento.
Todo mundo parece estar assumindo que esse i
é um tipo interno comum, como int
. Nesse caso, não haverá diferença mensurável.
No entanto, se i
for do tipo complexo, você poderá encontrar uma diferença mensurável. Pois i++
você deve fazer uma cópia da sua classe antes de incrementá-la. Dependendo do que está envolvido em uma cópia, pode ser realmente mais lento, pois ++it
você pode apenas 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 ++i
você tem a opção de retornar uma referência em vez de um valor. Novamente, dependendo do que estiver envolvido em fazer uma cópia do seu objeto, isso pode ser mais lento.
Um exemplo do mundo real de onde isso pode ocorrer seria o uso de iteradores. É pouco provável que copiar um iterador seja um gargalo no aplicativo, mas ainda é uma boa prática adquirir o hábito de usar em ++i
vez de i++
onde o resultado não é afetado.
Resposta curta:
Nunca há 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 na expressão encontrada.
No caso de for(i=0; i<n; i++)
, o i++
está sozinho em sua própria expressão: há um ponto de sequência antes do i++
e há um depois dele. Portanto, o único código de máquina gerado é "aumentar i
em 1
" e está bem definido como isso é sequenciado em relação ao restante do programa. Portanto, se você o alterasse para prefixo ++
, não importaria nem um pouco, ainda assim você obteria o código de máquina "aumentar i
em 1
".
As diferenças entre ++i
e i++
somente são importantes em expressões como array[i++] = x;
versus array[++i] = x;
. Alguns podem argumentar e dizer que o postfix será mais lento nessas operações porque o registro em que i
reside precisa ser recarregado mais tarde. Mas observe que o compilador é livre para solicitar suas instruções da maneira que desejar, desde que não "quebre o comportamento da máquina abstrata", como o padrão C chama.
Portanto, enquanto você pode assumir que array[i++] = x;
é traduzido para o código da máquina como:
i
no registro A.i
registrador A // ineficiente porque instruções extras aqui, já fizemos isso uma vez.i
.o compilador também pode produzir o código com mais eficiência, como:
i
no registro A.i
.Só porque você como programador C é treinado para pensar que o postfix ++
acontece no final, o código da máquina não precisa ser solicitado dessa maneira.
Portanto, não há diferença entre prefixo e postfix ++
em C. Agora, o que você como programador em C deve variar é de pessoas que inconsistentemente usam o prefixo em alguns casos e o postfix em outros casos, sem qualquer justificativa. Isso sugere que eles não sabem ao certo como o C funciona ou que têm conhecimento incorreto do idioma. Esse é sempre um mau sinal, pois sugere que eles estão tomando outras decisões questionáveis em seu programa, com base em superstições ou "dogmas religiosos".
"Prefixo ++
é sempre mais rápido" é, de fato, um desses dogmas falsos que é comum entre os possíveis programadores C.
Tomando uma folha de Scott Meyers, mais eficaz c ++ Item 6: Distinguir entre formas de prefixo e postfix de operações de incremento e decremento .
A versão do prefixo é sempre preferida ao postfix em relação aos objetos, especialmente em relação aos iteradores.
A razão para isso, se você observar o padrão de chamada dos operadores.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
Observando este exemplo, é fácil ver como o operador de prefixo sempre será mais eficiente que o postfix. Por causa da necessidade de um objeto 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á efetivamente nenhuma diferença devido à otimização do compilador que pode ocorrer.
Aqui está uma observação adicional se você estiver preocupado com a micro otimização. Decrementar loops pode 'possivelmente' ser mais eficiente 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 para:
1
a i
. i
é menor que a 100
.i
for menor que a 100
.Considerando que um loop decrescente:
for (i = 100; i != 0; i--)
O loop terá uma instrução para cada um dos seguintes:
i
, configurando o sinalizador de status do registro da CPU.Z==0
).Obviamente, isso funciona apenas ao diminuir para zero!
Lembrado no Guia do Desenvolvedor do ARM System.
Por favor, não deixe que a questão de "qual é o mais rápido" seja o fator decisivo a ser utilizado. Provavelmente, você nunca vai se importar tanto e, além disso, o tempo de leitura do programador é muito mais caro que o tempo da máquina.
Use o que fizer mais sentido para o ser humano que lê o código.
Primeiro de tudo: a diferença entre i++
e ++i
é negligenciável em C.
Para os detalhes.
++i
é mais rápidoEm C ++, ++i
é mais eficiente iff i
é algum tipo de objeto com um operador de incremento sobrecarregado.
Por quê?
Em ++i
, o objeto é incrementado primeiro e pode ser passado posteriormente como uma referência const para qualquer outra função. Isso não é possível se a expressão for foo(i++)
porque agora o incremento precisa ser feito antes da foo()
chamada, mas o valor antigo precisa ser passado para foo()
. Conseqüentemente, o compilador é forçado a fazer uma cópia i
antes de executar o operador de incremento no original. As chamadas adicionais de construtor / destruidor são a parte ruim.
Como observado acima, isso não se aplica a tipos fundamentais.
i++
pode ser mais rápidoSe nenhum construtor / destruidor precisar ser chamado, o que é sempre o caso em C ++i
e i++
deve ser igualmente rápido, certo? Não. Eles são virtualmente igualmente rápidos, mas pode haver pequenas diferenças, que a maioria dos respondentes entendeu errado.
Como pode i++
ser mais rápido?
O ponto é dependências de dados. Se o valor precisar ser carregado da memória, 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 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 da CPU, por isso é realmente negligenciável, mas está lá. E é o contrário, então muitos esperariam.
++i
ou i++
for usado em outra expressão, a alteração entre eles altera a semântica da expressão, portanto, qualquer ganho / perda de desempenho possível está fora de questão. Se eles forem autônomos, ou seja, o resultado da operação não for usado imediatamente, qualquer compilador decente irá compilá-lo da mesma maneira, por exemplo, uma INC
instrução de montagem.
i++
e ++i
podem ser usados alternadamente em quase todas as situações possíveis, ajustando constantes loop por um, então eles são equivalente mais próximo no que eles fazem para o programador. 2) Embora ambos sejam compilados com a mesma instrução, sua execução difere para a CPU. No caso de i++
, a CPU pode calcular o incremento paralelamente a alguma outra instrução que usa o mesmo valor (as CPUs realmente fazem isso!), Enquanto ++i
a CPU precisa agendar a outra instrução após o incremento.
if(++foo == 7) bar();
e if(foo++ == 6) bar();
são funcionalmente equivalentes. No entanto, o segundo pode ser um ciclo mais rápido, porque a comparação e o incremento podem ser calculados em paralelo pela CPU. Não que esse ciclo único tenha muita importância, mas a diferença está aí.
<
por exemplo, vs <=
) onde ++
é geralmente usado, portanto a conversão entre o pensamento é geralmente possível.
@ Mark Mesmo que o compilador possa otimizar a cópia temporária (baseada em pilha) da variável e o gcc (nas versões recentes) o esteja fazendo, isso não significa que todos os compiladores sempre o farão.
Acabei de testá-lo com os compiladores que usamos em nosso projeto atual e 3 em cada 4 não o otimizam.
Nunca assuma que o compilador esteja certo, especialmente se o código possivelmente mais rápido, mas nunca mais lento, for tão fácil de ler.
Se você não tem uma implementação realmente estúpida de um dos operadores no seu código:
Sempre preferi ++ i sobre i ++.
Em C, o compilador geralmente pode otimizá-los para que sejam iguais, se o resultado não for utilizado.
No entanto, em C ++, se estiver usando outros tipos que fornecem seus próprios operadores ++, a versão do prefixo provavelmente será mais rápida que a versão do postfix. Portanto, se você não precisar da semântica do postfix, é melhor usar o operador prefix.
Posso pensar em uma situação em que o postfix é mais lento que o incremento de prefixo:
Imagine que um processador com registro A
seja usado como acumulador e seja o único registro usado em muitas instruções (alguns microcontroladores pequenos são assim).
Agora imagine o seguinte programa e sua tradução em uma montagem hipotética:
Incremento do 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 do 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 prosseguir com o uso, possivelmente evitar recarregá-lo, pois o valor desejado já está no registro após o incremento. No entanto, com o incremento do postfix, o compilador precisa lidar com dois valores, um antigo e o valor incrementado que, como mostro acima, resulta em mais um acesso à memória.
Obviamente, se o valor do incremento não for usado, como uma única i++;
instrução, o compilador pode (e gera) simplesmente gerar uma instrução de incremento, independentemente do uso do postfix ou do prefixo.
Como uma observação lateral, eu gostaria de mencionar que uma expressão na qual existe b++
não pode ser simplesmente convertida em uma ++b
sem esforço adicional (por exemplo, adicionando a - 1
). Portanto, comparar os dois se eles fazem parte de alguma expressão não é realmente válido. Muitas vezes, onde você usa b++
dentro de uma expressão que não pode usar ++b
, mesmo que ++b
fosse potencialmente mais eficiente, isso seria errado. Exceção é claro se a expressão está implorando por ela (por exemplo, a = b++ + 1;
que pode ser alterada para a = ++b;
).
Eu tenho lido pela maioria das respostas aqui e muitos dos comentários, e eu não vi qualquer referência à um exemplo que eu poderia pensar de onde i++
é mais eficiente do que ++i
(e talvez surpreendentemente --i
foi mais eficiente do que i--
). Isso é para compiladores C para o DEC PDP-11!
O PDP-11 tinha instruções de montagem para pré-decremento de um registro e pós-incremento, mas não o contrário. As instruções permitiram que qualquer registro "de uso geral" fosse usado como ponteiro de pilha. Portanto, se você usasse algo parecido, *(i++)
ele poderia ser compilado em uma única instrução de montagem, enquanto *(++i)
não poderia.
Este é obviamente um exemplo muito esotérico, mas fornece a exceção em que o pós-incremento é mais eficiente (ou eu deveria dizer que foi , já que não há muita demanda pelo código C do PDP-11 atualmente).
--i
e i++
.
Eu sempre prefiro pré-incremento, no entanto ...
Queria ressaltar que, mesmo no caso de chamar a função operator ++, o compilador poderá otimizar o temporário se a função for incorporada. Como o operador ++ é geralmente curto e frequentemente implementado no cabeçalho, é provável que ele seja incorporado.
Portanto, para fins práticos, provavelmente não há muita diferença entre o desempenho das duas formas. No entanto, eu 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, dar menos chances ao otimizador significa que o compilador será executado mais rapidamente.
Meu C está um pouco enferrujado, então peço desculpas antecipadamente. Speedwise, eu posso entender os resultados. Mas estou confuso sobre como os dois arquivos saíram com o mesmo hash MD5. Talvez um loop for execute o mesmo, mas as 2 linhas de código a seguir não gerariam um assembly diferente?
myArray[i++] = "hello";
vs
myArray[++i] = "hello";
O primeiro grava o valor na matriz e depois incrementa i. Os segundos incrementos são gravados na matriz. Não sou especialista em montagem, mas não vejo como o mesmo executável seria gerado por essas duas linhas de código diferentes.
Apenas meus dois centavos.
foo[i++]
para foo[++i]
sem alterar qualquer outra coisa obviamente mudaria a semântica do programa, mas em alguns processadores ao usar um compilador sem uma lógica de otimização de elevação de loop, incrementar p
e q
executar uma vez e depois executar um loop que executa, por exemplo, *(p++)=*(q++);
seria mais rápido do que usar um loop que executa *(++pp)=*(++q);
. Para loops muito apertados em alguns processadores, a diferença de velocidade pode ser significativa (mais de 10%), mas esse é provavelmente o único caso em C em que o pós-incremento é materialmente mais rápido que o pré-incremento.