Temos a pergunta : existe uma diferença de desempenho entre i++
e ++i
em C ?
Qual é a resposta para C ++?
c++
performance
oop
post-increment
pre-increment
Mark Harrison
fonte
fonte
Respostas:
[Resumo executivo: use
++i
se você não tiver um motivo específico para usá-loi++
.]Para C ++, a resposta é um pouco mais complicada.
Se
i
for um tipo simples (não uma instância de uma classe C ++), a resposta dada para C ("Não há diferença de desempenho") é válida, pois o compilador está gerando o código.No entanto, se
i
é uma instância de uma classe C ++, entãoi++
e++i
está fazendo chamadas para uma dasoperator++
funções. Aqui está um par padrão dessas funções:Como o compilador não está gerando código, mas apenas chamando uma
operator++
função, não há como otimizar atmp
variável e seu construtor de cópias associado. Se o construtor de cópias for caro, isso poderá ter um impacto significativo no desempenho.fonte
Sim. Há sim.
O operador ++ pode ou não ser definido como uma função. Para tipos primitivos (int, double, ...), os operadores são incorporados, portanto o compilador provavelmente poderá otimizar seu código. Mas no caso de um objeto que define o operador ++, as coisas são diferentes.
A função do operador ++ (int) deve criar uma cópia. Isso ocorre porque o postfix ++ deve retornar um valor diferente do que contém: ele deve reter seu valor em uma variável temp, incrementar seu valor e retornar a temp. No caso do operador ++ (), prefixo ++, não há necessidade de criar uma cópia: o objeto pode se incrementar e simplesmente retornar a si mesmo.
Aqui está uma ilustração do ponto:
Toda vez que você chama o operador ++ (int), deve criar uma cópia, e o compilador não pode fazer nada sobre isso. Quando for dada a opção, use operator ++ (); Dessa forma, você não salva uma cópia. Pode ser significativo no caso de muitos incrementos (loop grande?) E / ou objetos grandes.
fonte
C t(*this); ++(*this); return t;
Na segunda linha, você está incrementando o ponteiro this right, então como ét
atualizado se você está incrementando isso. Os valores disso já não foram copiadost
?The operator++(int) function must create a copy.
não não é. Não há mais cópias do queoperator++()
Aqui está uma referência para o caso em que os operadores de incremento estão em diferentes unidades de tradução. Compilador com g ++ 4.5.
Ignore os problemas de estilo por enquanto
Incremento O (n)
Teste
Resultados
Resultados (os tempos são em segundos) com o g ++ 4.5 em uma máquina virtual:
Incremento de O (1)
Teste
Vamos agora pegar o seguinte arquivo:
Não faz nada no incremento. Isso simula o caso em que o incremento tem complexidade constante.
Resultados
Os resultados agora variam extremamente:
Conclusão
Em termos de desempenho
Se você não precisar do valor anterior, crie o hábito de usar o pré-incremento. Seja consistente mesmo com os tipos internos, você se acostumará e não corre o risco de sofrer uma perda desnecessária de desempenho se substituir um tipo interno por um personalizado.
Semântica
i++
dizincrement i, I am interested in the previous value, though
.++i
dizincrement i, I am interested in the current value
ouincrement i, no interest in the previous value
. Mais uma vez, você se acostumará, mesmo que não esteja no momento.Knuth.
Otimização prematura é a raiz de todo o mal. Como é a pessimização prematura.
fonte
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
, independentemente da estrutura da árvore real (BSP, kd, Quadtree, Octree Grid, etc.). Tal iterador seria necessário para manter um estado, por exemploparent node
,child node
,index
e coisas assim. Ao todo, a minha posição é que, mesmo se houver apenas alguns exemplos, ...Não é totalmente correto dizer que o compilador não pode otimizar a cópia variável temporária no caso do postfix. Um teste rápido com VC mostra que, pelo menos, pode fazer isso em certos casos.
No exemplo a seguir, o código gerado é idêntico para prefixo e postfix, por exemplo:
Quer você faça ++ testFoo ou testFoo ++, ainda assim obterá o mesmo código resultante. De fato, sem ler a contagem do usuário, o otimizador reduziu a coisa toda a uma constante. Então, é isso:
Resultou no seguinte:
Portanto, embora certamente a versão do postfix possa ser mais lenta, pode ser que o otimizador seja bom o suficiente para se livrar da cópia temporária, se você não a estiver usando.
fonte
O Guia de Estilo do Google C ++ diz:
fonte
Gostaria de destacar um excelente post de Andrew Koenig no Code Talk muito recentemente.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
Em nossa empresa, também usamos a convenção do ++ iter para consistência e desempenho, quando aplicável. Mas Andrew levanta detalhes negligenciados em relação à intenção versus desempenho. Há momentos em que queremos usar o iter ++ em vez do ++ iter.
Portanto, primeiro decida sua intenção e, se pre ou post não interessar, vá com pre, pois ele terá algum benefício no desempenho, evitando a criação de um objeto extra e o jogando.
fonte
@Ketan
Obviamente, pós e pré-incremento têm semânticas diferentes e tenho certeza de que todos concordam que quando o resultado é usado, você deve usar o operador apropriado. Eu acho que a pergunta é o que devemos fazer quando o resultado é descartado (como em
for
loops). A resposta a esta pergunta (IMHO) é que, como as considerações de desempenho são insignificantes, você deve fazer o que é mais natural. Para mim++i
é mais natural, mas minha experiência me diz que sou uma minoria e o usoi++
causará menos sobrecarga de metal para a maioria pessoas que lê seu código.Afinal, é por isso que o idioma não é chamado "
++C
". [*][*] Insira uma discussão obrigatória sobre
++C
ser um nome mais lógico.fonte
Quando não estiver usando o valor de retorno, é garantido que o compilador não use um temporário no caso de ++ i . Não é garantido que seja mais rápido, mas que não será mais lento.
Ao usar o valor de retorno, o i ++ permite que o processador introduza o incremento e o lado esquerdo no pipeline, pois eles não dependem um do outro. ++ i pode parar o pipeline porque o processador não pode iniciar o lado esquerdo até que a operação de pré-incremento tenha se espalhado por todo o caminho. Novamente, não há garantia de uma paralisação de pipeline, pois o processador pode encontrar outras coisas úteis para se manter.
fonte
Mark: Só queria ressaltar que os operadores ++ são bons candidatos a serem incorporados e, se o compilador optar por fazê-lo, a cópia redundante será eliminada na maioria dos casos. (por exemplo, tipos de POD, que geralmente são os iteradores.)
Dito isto, ainda é melhor usar o ++ iter na maioria dos casos. :-)
fonte
A diferença de desempenho entre
++i
ei++
será mais aparente quando você considerar os operadores como funções de retorno de valor e como elas são implementadas. Para facilitar a compreensão do que está acontecendo, os seguintes exemplos de código serão usadosint
como se fosse umstruct
.++i
incrementa a variável e retorna o resultado. Isso pode ser feito no local e com tempo mínimo de CPU, exigindo apenas uma linha de código em muitos casos:Mas o mesmo não pode ser dito
i++
.Pós-incremento,,
i++
geralmente é visto como retornando o valor original antes de incrementar. No entanto, uma função só pode retornar um resultado quando estiver concluída . Como resultado, torna-se necessário criar uma cópia da variável que contém o valor original, incrementar a variável e retornar a cópia mantendo o valor original:Quando não há diferença funcional entre pré-incremento e pós-incremento, o compilador pode executar a otimização para que não haja diferença de desempenho entre os dois. No entanto, se um tipo de dados composto como a
struct
ouclass
estiver envolvido, o construtor de cópia será chamado no pós-incremento e não será possível executar essa otimização se uma cópia profunda for necessária. Como tal, o pré-incremento geralmente é mais rápido e requer menos memória que o pós-incremento.fonte
@ Mark: eu apaguei minha resposta anterior, porque foi um pouco invertida, e merecia um voto negativo apenas por isso. Na verdade, acho que é uma boa pergunta, no sentido de perguntar o que está na cabeça de muitas pessoas.
A resposta usual é que ++ i é mais rápido que i ++, e sem dúvida é, mas a grande questão é "quando você deve se importar?"
Se a fração do tempo da CPU gasto no incremento de iteradores for menor que 10%, talvez você não se importe.
Se a fração do tempo de CPU gasto no incremento de iteradores for maior que 10%, você poderá verificar quais instruções estão fazendo essa iteração. Veja se você pode apenas incrementar números inteiros em vez de usar iteradores. As chances são de que você poderia, e embora possa ser, de certo modo, menos desejável, as chances são muito boas, você economizará essencialmente todo o tempo gasto nesses iteradores.
Eu vi um exemplo em que o incremento do iterador estava consumindo bem mais de 90% do tempo. Nesse caso, ir para o incremento inteiro reduziu o tempo de execução essencialmente por esse valor. (ou seja, melhor que 10x aceleração)
fonte
@wilhelmtell
O compilador pode excluir o temporário. Verbatim do outro segmento:
O compilador C ++ tem permissão para eliminar temporários baseados em pilha, mesmo que isso mude o comportamento do programa. Link MSDN para VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
fonte
Um dos motivos pelos quais você deve usar o ++ i, mesmo nos tipos internos em que não há vantagem de desempenho, é criar um bom hábito para si mesmo.
fonte
Ambos são tão rápidos;) Se você deseja que seja o mesmo cálculo para o processador, é apenas a ordem na qual isso é feito.
Por exemplo, o seguinte código:
Produza o seguinte conjunto:
Você vê que, para a ++ e b ++, é um mnemônico incl. Portanto, é a mesma operação;)
fonte
A pergunta pretendida era sobre quando o resultado não é utilizado (isso fica claro na pergunta para C). Alguém pode consertar isso, pois a pergunta é "wiki da comunidade"?
Sobre otimizações prematuras, Knuth é frequentemente citado. Está certo. mas Donald Knuth nunca defenderia com esse código horrível que você pode ver nestes dias. Já viu a = b + c entre inteiros Java (não int)? Isso equivale a três conversões de boxe / unboxing. Evitar coisas assim é importante. E escrever inutilmente i ++ em vez de ++ i é o mesmo erro. EDIT: Como o phresnel coloca bem em um comentário, isso pode ser resumido como "otimização prematura é má, assim como pessimização prematura".
Mesmo o fato de as pessoas estarem mais acostumadas ao i ++ é um infeliz legado de C, causado por um erro conceitual da K&R (se você seguir o argumento da intenção, essa é uma conclusão lógica; defender a K&R porque é K&R não tem sentido, eles são ótimo, mas eles não são ótimos como designers de linguagem; existem inúmeros erros no design C, variando de gets () a strcpy (), até a API strncpy () (ela deveria ter a API strlcpy () desde o primeiro dia) )
Btw, eu sou um daqueles que não são usados o suficiente para C ++ para encontrar ++ eu chato de ler. Ainda assim, eu uso isso porque reconheço que está certo.
fonte
++i
mais chato do quei++
(na verdade, achei mais legal), mas o resto do seu post recebe meu total reconhecimento. Talvez adicionar um ponto de "otimização prematura é mau, como é pessimization prematuro"strncpy
serviu a um propósito nos sistemas de arquivos que eles estavam usando na época; o nome do arquivo era um buffer de 8 caracteres e não precisava ser terminado por nulo. Você não pode culpá-los por não verem 40 anos no futuro da evolução da linguagem.strlcpy()
foi justificada pelo fato de ainda não ter sido inventada.Hora de fornecer às pessoas gemas de sabedoria;) - existe um truque simples para fazer com que o incremento do postfix do C ++ se comporte da mesma forma que o incremento de prefixo (inventado por mim mesmo, mas o vi também no código de outras pessoas, por isso não estou sozinho).
Basicamente, o truque é usar a classe auxiliar para adiar o incremento após o retorno, e o RAII vem para resgatar
Inventado é para alguns códigos pesados de iteradores personalizados e reduz o tempo de execução. O custo do prefixo versus o postfix é uma referência agora e, se esse é um operador personalizado que faz movimentos pesados, o prefixo e o postfix produziram o mesmo tempo de execução para mim.
fonte
++i
é mais rápido do quei++
porque não retorna uma cópia antiga do valor.Também é mais intuitivo:
Este exemplo C imprime "02" em vez dos "12" que você pode esperar:
O mesmo para C ++ :
fonte