Pergunta

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

Foi útil?

Solução

Sumário executivo:Não.

i++ poderia ser potencialmente mais lento do que ++i, já que o antigo valor de iPode ser necessário ser salvo para uso posterior, mas na prática todos os compiladores modernos otimizarão isso.

Podemos demonstrar isso olhando o código para esta função, ambos 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 tanto o objeto gerado quanto os arquivos assembler são iguais.

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22

Outras dicas

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

Primeiro, está longe de ser óbvio que ++i é mais eficiente do que i++, 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 sim qual dessas duas operações expressa com mais precisão o que você está tentando realizar.Afirmo que se você não estiver usando o valor da expressão, nunca haverá 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 descartar a cópia.

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

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

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

Contudo se i é do tipo complexo, então você pode encontrar uma diferença mensurável.Para i++ você deve fazer uma cópia da sua classe antes de incrementá-la.Dependendo do que está envolvido em uma cópia, ela pode realmente ser mais lenta, pois com ++it você pode simplesmente 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 na cópia do seu objeto, isso pode ser mais lento.

Um exemplo 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 em vez de i++ onde o resultado não é afetado.

Seguindo o exemplo de Scott Meyers, C++ mais eficaz Item 6:Distinguir entre formas prefixadas e pós-fixadas de operações de incremento e decremento.

A versão prefixada é sempre preferida ao postfix em relação a objetos, especialmente em relação a iteradores.

A razão para isso se você observar o padrão de chamadas das operadoras.

// 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 prefixo será sempre mais eficiente que o postfix.Devido à 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ê apontou para int, efetivamente não há 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 exemploARM), dado:

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

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

  1. Adicionando 1 para i.
  2. Compare se i é menos que um 100.
  3. Uma ramificação condicional se i é menos que um 100.

Considerando que um loop decrescente:

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

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

  1. Diminuir i, definindo o sinalizador de status do registro da CPU.
  2. Uma ramificação condicional dependendo do status do registro da CPU (Z==0).

É claro que isso funciona apenas quando decrementamos para zero!

Lembrado do Guia do desenvolvedor do sistema ARM.

Resposta curta:

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

Resposta longa:

O que todas as outras respostas deixam de mencionar é que a diferença entre ++i contra i++ só faz sentido dentro da expressão que é encontrada.

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

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

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

  • Valor armazenado de i no registro A.
  • Armazene o endereço da matriz no registro B.
  • Adicione A e B, armazene os resultados em A.
  • Neste novo endereço representado por A, armazene o valor de x.
  • Valor armazenado de i no registro A // ineficiente porque instrução extra aqui, já fizemos isso uma vez.
  • Registro de incremento A.
  • Armazene o registro A em i.

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

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

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 ordenado dessa forma.

Portanto não há diferença entre prefixo e postfix ++ em C.Agora, o que você, como programador C, deve variar são pessoas que usam prefixo de forma inconsistente em alguns casos e postfix em outros casos, sem qualquer justificativa.Isto sugere que eles não têm certeza sobre como C funciona ou que têm um conhecimento incorreto da linguagem.Isto é sempre um mau sinal, pois por sua vez sugere que estão a tomar outras decisões questionáveis ​​no seu programa, baseadas em superstições ou "dogmas religiosos".

"Prefixo ++ é sempre mais rápido" é de fato um falso dogma comum entre aspirantes a programadores C.

Por favor, não deixe que a questão de "qual é o mais rápido" seja o fator decisivo sobre qual usar.Provavelmente, você nunca 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.

Em primeiro lugar:A diferença entre i++ e ++i é insignificante em C.


Para os detalhes.

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

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

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

Conforme observado acima, isso não se aplica a tipos fundamentais.

2.O fato pouco conhecido: i++ poderia seja mais rápido

Se 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 praticamente igualmente rápidos, mas pode haver pequenas diferenças, que a maioria dos outros respondentes entendeu errado.

Como pode i++ seja mais rápido?
A questão são as dependências de dados.Se o valor precisar ser carregado da memória, duas operações subsequentes deverão ser feitas com ele, incrementando-o e utilizando-o.Com ++i, o incremento precisa ser feito antes o valor pode ser usado.Com i++, o uso não depende do incremento, e a CPU pode realizar a operação de uso em paralelo para a operação de incremento.A diferença é de no máximo um ciclo de CPU, então é realmente insignificante, mas existe.E é o contrário do que muitos esperariam.

@Mark Embora o compilador possa otimizar a cópia temporária (baseada em pilha) da variável e do GCC (em versões recentes) está fazendo isso, não significa todos compiladores sempre farão isso.

Acabei de testá-lo com os compiladores que usamos em nosso projeto atual e 3 em cada 4 não o otimizam.

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

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

Sempre preferi ++i a 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 usar outros tipos que fornecem 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 prefixo.

Posso pensar em uma situação em que o postfix é mais lento que o incremento do prefixo:

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

Agora imagine o seguinte programa e sua tradução em uma montagem 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 pós-fixado:

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 sua utilização, possivelmente evitando recarregá-lo, pois o valor desejado já está no registrador após o incremento.Porém, com o incremento postfix, o compilador tem que lidar com dois valores, um o antigo e outro o valor incrementado que como mostro acima resulta em mais um acesso à memória.

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


Como observação lateral, gostaria de mencionar que uma expressão em que há um b++ não pode simplesmente ser convertido em um com ++b sem qualquer esforço adicional (por exemplo, adicionando um - 1).Portanto, comparar os dois se fazem parte de alguma expressão não é realmente válido.Muitas vezes, onde você usa b++ dentro de uma expressão que você não pode usar ++b, então mesmo que ++b fossem potencialmente mais eficientes, seria simplesmente errado.A exceção é, claro, se a expressão estiver implorando por isso (por exemplo a = b++ + 1; que pode ser alterado para a = ++b;).

Eu sempre prefiro o pré-incremento, porém ...

Queria ressaltar que mesmo no caso de chamar a função operador++, o compilador será capaz de 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 fique embutido.

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

Além disso, dar menos tarefas ao otimizador provavelmente significa que o compilador será executado mais rapidamente.

Meu C está um pouco enferrujado, então peço desculpas antecipadamente.Rapidamente, posso entender os resultados.Mas estou confuso sobre como os dois arquivos chegaram ao mesmo hash MD5.Talvez um loop for seja executado da mesma forma, mas as duas linhas de código a seguir não gerariam uma montagem diferente?

myArray[i++] = "hello";

contra

myArray[++i] = "hello";

O primeiro grava o valor no array e depois incrementa i.Os segundos incrementos são então gravados no array.Não sou especialista em montagem, mas simplesmente não vejo como o mesmo executável seria gerado por essas duas linhas de código diferentes.

Apenas meus dois centavos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top