É x + = a mais rápido do que x = x + a?


84

Eu estava lendo "The C ++ Programming Language" de Stroustrup, onde ele diz que de duas maneiras de adicionar algo a uma variável

x = x + a;

e

x += a;

Ele prefere +=porque provavelmente é melhor implementado. Acho que ele quer dizer que funciona mais rápido também.
Mas é mesmo? Se depender do compilador e outras coisas, como faço para verificar?


45
"The C ++ Programming Language" foi publicado pela primeira vez em 1985. A versão mais recente foi publicada em 1997, e uma edição especial da versão de 1997 foi publicada em 2000. Como conseqüência, algumas partes estão extremamente desatualizadas.
JoeG de

5
As duas linhas podem fazer algo completamente diferente. Você tem que ser mais específico.
Kerrek SB de


26
Compiladores modernos são inteligentes o suficiente para que essas questões sejam consideradas 'desatualizadas'.
gd1

2
Reaberto porque a pergunta duplicada pergunta sobre C e não C ++.
Kev

Respostas:


212

Qualquer valor de compilador o seu sal vai gerar exatamente a mesma seqüência de linguagem de máquina para ambas as construções para qualquer tipo built-in ( int, float, etc), desde que a declaração é realmente tão simples como x = x + a; e otimização está habilitado . (Notavelmente, o GCC -O0, que é o modo padrão, realiza anti-otimizações , como inserir armazenamentos completamente desnecessários na memória, a fim de garantir que os depuradores sempre possam encontrar valores de variáveis.)

Se a declaração for mais complicada, porém, eles podem ser diferentes. Suponha que fseja uma função que retorna um ponteiro, então

*f() += a;

liga fapenas uma vez, enquanto

*f() = *f() + a;

chama duas vezes. Se ftiver efeitos colaterais, um dos dois estará errado (provavelmente o último). Mesmo se fnão tiver efeitos colaterais, o compilador pode não ser capaz de eliminar a segunda chamada, portanto, a última pode ser mais lenta.

E como estamos falando sobre C ++ aqui, a situação é totalmente diferente para tipos de classes que sobrecarregam operator+e operator+=. Se xfor esse tipo, então - antes da otimização - se x += atraduz em

x.operator+=(a);

Considerando que se x = x + atraduz em

auto TEMP(x.operator+(a));
x.operator=(TEMP);

Agora, se a classe for escrita corretamente e o otimizador do compilador for bom o suficiente, ambos acabarão gerando a mesma linguagem de máquina, mas não é uma coisa certa como é para tipos internos. Provavelmente é nisso que Stroustrup está pensando quando incentiva o uso de +=.


14
Há também outro aspecto - legibilidade . Linguagem C ++ para adicionar expra varé var+=expre escrevê-lo para o outro lado vai confundir os leitores.
Tadeusz Kopec

21
Se você estiver escrevendo, *f() = *f() + a;talvez queira dar uma boa olhada no que você realmente está tentando alcançar ...
Adam Davis

3
E se var = var + expr confunde você, mas var + = expr não, você é o engenheiro de software mais estranho que já conheci. Ambos são legíveis; apenas certifique-se de que você é consistente (e todos nós usamos op =, então é discutível de qualquer maneira = P)
WhozCraig

7
@PiotrDobrogost: O que há de errado em responder perguntas? Em qualquer caso, deve ser o questionador que verifica se há duplicatas.
Gorpik

4
@PiotrDobrogost me parece que você está um pouco ... ciumento ... Se você quiser sair por aí procurando por duplicatas, vá em frente. Eu, por exemplo, prefiro responder às perguntas ao invés de procurar pessoas ingênuas (a menos que seja uma pergunta que eu especificamente me lembro de ter visto antes). Às vezes pode ser mais rápido e, portanto, ajude quem fez a pergunta mais rápido. Além disso, observe que isso nem mesmo é um loop. 1é uma constante, apode ser volátil, um tipo definido pelo usuário ou qualquer outra coisa. Completamente diferente. Na verdade, não entendo como isso foi encerrado.
Luchian Grigore

56

Você pode verificar olhando para a desmontagem, que será a mesma.

Para tipos básicos , ambos são igualmente rápidos.

Esta é a saída gerada por uma compilação de depuração (ou seja, sem otimizações):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

Para tipos definidos pelo usuário , onde você pode sobrecarregar operator +e operator +=, depende de suas respectivas implementações.


1
Não é verdade em todos os casos. Descobri que pode ser mais rápido carregar um endereço de memória em um registro, incrementá-lo e gravá-lo de volta do que incrementar a localização da memória diretamente (sem usar átomos). Vou tentar arranjar o código ...
James,

E os tipos definidos pelo usuário? Bons compiladores devem gerar montagem equivalente, mas não há tal garantia.
mfontanini,

1
@LuchianGrigore Não, se afor 1 e xfor volatileo compilador poderia gerar inc DWORD PTR [x]. Isso é lento.
James,

1
@Chiffa não é dependente do compilador, mas sim do desenvolvedor. Você poderia implementar operator +não fazer nada e operator +=calcular o número 100000º primo e depois retornar. Claro, isso seria uma coisa estúpida de se fazer, mas é possível.
Luchian Grigore

3
@James: se o seu programa é sensível à diferença de desempenho entre ++xe temp = x + 1; x = temp;, então provavelmente ele deve ser escrito em assembly em vez de c ++ ...
EmirCalabuch

11

Sim! É mais rápido escrever, ler e descobrir, para o último caso xpossa ter efeitos colaterais. Portanto, é no geral mais rápido para os humanos. O tempo humano em geral custa muito mais do que o tempo do computador, então deve ser sobre isso que você está perguntando. Direito?


8

Realmente depende do tipo de xea e da implementação de +. Para

   T x, a;
   ....
   x = x + a;

o compilador deve criar um T temporário para conter o valor de x + a enquanto o avalia, que pode então atribuir a x. (Não pode usar x ou a como área de trabalho durante esta operação).

Para x + = a, não precisa de um temporário.

Para tipos triviais, não há diferença.


8

A diferença entre x = x + ae x += aé a quantidade de trabalho que a máquina tem que passar - alguns compiladores podem (e geralmente fazem) otimizá-la, mas geralmente, se ignorarmos a otimização por um tempo, o que acontece é que no trecho de código anterior, o a máquina deve pesquisar o valor xduas vezes, enquanto na última, essa pesquisa precisa ocorrer apenas uma vez.

No entanto, como mencionei, hoje a maioria dos compiladores são inteligentes o suficiente para analisar a instrução e reduzir as instruções de máquina resultantes necessárias.

PS: Primeira resposta no Stack Overflow!


6

Como você rotulou este C ++, não há como saber a partir das duas declarações que você postou. Você precisa saber o que é 'x' (é um pouco como a resposta '42'). Se xfor um POD, não fará muita diferença. No entanto, se xfor uma classe, pode haver sobrecargas para os métodos operator +e operator +=que podem ter comportamentos diferentes que levam a tempos de execução muito diferentes.


6

Se você disser +=que está tornando a vida muito mais fácil para o compilador. Para que o compilador reconheça que x = x+aé o mesmo que x += a, o compilador deve

  • analise o lado esquerdo ( x) para ter certeza de que não há efeitos colaterais e sempre se refere ao mesmo valor l. Por exemplo, pode ser z[i], e tem que garantir que ambos ze inão mudem.

  • analise o lado direito ( x+a) e certifique-se de que é um somatório, e que o lado esquerdo ocorre uma vez e apenas uma vez no lado direito, embora possa ser transformado, como em z[i] = a + *(z+2*0+i).

Se o que você quer dizer é adicionar aa x, o redator do compilador agradece quando você apenas diz o que quer dizer. Dessa forma, você não está exercitando a parte do compilador que seu escritor espera que ele / ela tem todos os bugs fora de, e que não realmente tornar a vida mais fácil para você, a menos que você honestamente não pode obter a cabeça para fora do modo Fortran.


5

Para um exemplo concreto, imagine um tipo de número complexo simples:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

Para o caso a = a + b, ele tem que fazer uma variável temporária extra e então copiá-la.


este é um exemplo muito bom, mostra como 2 operadores implementados.
Grijesh Chauhan

5

Você está fazendo a pergunta errada.

É improvável que isso impulsione o desempenho de um aplicativo ou recurso. Mesmo se fosse, a maneira de descobrir é analisar o código e saber como ele o afeta com certeza. Em vez de se preocupar nesse nível com o que é mais rápido, é muito mais importante pensar em termos de clareza, correção e legibilidade.

Isso é especialmente verdadeiro quando você considera que, mesmo que seja um fator de desempenho significativo, os compiladores evoluem com o tempo. Alguém pode descobrir uma nova otimização e a resposta certa hoje pode se tornar errada amanhã. É um caso clássico de otimização prematura.

Isso não quer dizer que o desempenho não tenha importância alguma ... Apenas que é a abordagem errada para atingir seus objetivos de desempenho. A abordagem certa é usar ferramentas de criação de perfil para aprender onde seu código está realmente gastando seu tempo e, portanto, onde concentrar seus esforços.


Concedido, mas era uma questão de baixo nível, não um quadro geral do tipo "Quando devo considerar tal diferença".
Chiffa

1
A pergunta do OP era totalmente legítima, como mostrado pelas respostas (e votos positivos) de outros. Embora entendamos seu ponto (primeiro perfil, etc.), definitivamente é interessante saber esse tipo de coisa - você realmente vai traçar um perfil de cada afirmação trivial que você escreve, traçar um perfil dos resultados de cada decisão que você toma? Mesmo quando há pessoas no SO que já estudaram, traçaram o perfil, desmontaram o caso e têm uma resposta geral para dar?

4

Acho que isso deve depender da máquina e de sua arquitetura. Se sua arquitetura permitir endereçamento indireto de memória, o redator do compilador PODE usar este código (para otimização):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

Visto que, i = i + ypode ser traduzido para (sem otimização):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


Dito isso, outras complicações, como se ié um ponteiro de retorno de função, etc., também devem ser consideradas. A maioria dos compiladores de nível de produção, incluindo GCC, produzem o mesmo código para ambas as instruções (se forem inteiros).


2

Não, as duas maneiras são tratadas da mesma forma.


10
Não se for um tipo definido pelo usuário com operadores sobrecarregados.
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.