Vou contra a sabedoria geral aqui que std::copy
terá uma perda de desempenho leve, quase imperceptível. Acabei de fazer um teste e achei que era falso: notei uma diferença de desempenho. No entanto, o vencedor foi std::copy
.
Eu escrevi uma implementação C ++ SHA-2. No meu teste, fiz o hash de 5 strings usando todas as quatro versões do SHA-2 (224, 256, 384, 512) e faço loop 300 vezes. Eu medo os tempos usando o Boost.timer. Esse contador de 300 laços é suficiente para estabilizar completamente meus resultados. Eu executei o teste 5 vezes cada, alternando entre a memcpy
versão e a std::copy
versão. Meu código aproveita a captura de dados no maior número possível de partes (muitas outras implementações operam com char
/ char *
, enquanto eu opero com T
/ T *
(onde T
é o maior tipo na implementação do usuário que tem o comportamento correto de estouro)), para um acesso rápido à memória no O maior número de tipos possíveis é essencial para o desempenho do meu algoritmo. Estes são os meus resultados:
Tempo (em segundos) para concluir a execução dos testes SHA-2
std::copy memcpy % increase
6.11 6.29 2.86%
6.09 6.28 3.03%
6.10 6.29 3.02%
6.08 6.27 3.03%
6.08 6.27 3.03%
Aumento médio total na velocidade de std :: copy over memcpy: 2.99%
Meu compilador é o gcc 4.6.3 no Fedora 16 x86_64. Meus sinalizadores de otimização são -Ofast -march=native -funsafe-loop-optimizations
.
Código para minhas implementações SHA-2.
Decidi também executar um teste na minha implementação MD5. Os resultados foram muito menos estáveis, então decidi fazer 10 corridas. No entanto, após minhas primeiras tentativas, obtive resultados que variaram bastante de uma corrida para a outra, então acho que havia algum tipo de atividade do SO em andamento. Eu decidi começar de novo.
Mesmas configurações e sinalizadores do compilador. Existe apenas uma versão do MD5 e é mais rápida que o SHA-2, então eu fiz 3000 loops em um conjunto semelhante de 5 sequências de teste.
Estes são os meus 10 resultados finais:
Tempo (em segundos) para concluir a execução dos testes MD5
std::copy memcpy % difference
5.52 5.56 +0.72%
5.56 5.55 -0.18%
5.57 5.53 -0.72%
5.57 5.52 -0.91%
5.56 5.57 +0.18%
5.56 5.57 +0.18%
5.56 5.53 -0.54%
5.53 5.57 +0.72%
5.59 5.57 -0.36%
5.57 5.56 -0.18%
Redução média total na velocidade de std :: copy over memcpy: 0.11%
Código para minha implementação MD5
Esses resultados sugerem que há alguma otimização que std :: copy usada nos meus testes SHA-2 que std::copy
não pôde ser usada nos meus testes MD5. Nos testes SHA-2, ambas as matrizes foram criadas na mesma função que chamou std::copy
/ memcpy
. Nos meus testes MD5, uma das matrizes foi passada para a função como um parâmetro de função.
Fiz um pouco mais de teste para ver o que eu poderia fazer para std::copy
acelerar mais rapidamente. A resposta acabou sendo simples: ative a otimização do tempo do link. Estes são meus resultados com o LTO ativado (opção -flto no gcc):
Tempo (em segundos) para concluir a execução dos testes MD5 com -flto
std::copy memcpy % difference
5.54 5.57 +0.54%
5.50 5.53 +0.54%
5.54 5.58 +0.72%
5.50 5.57 +1.26%
5.54 5.58 +0.72%
5.54 5.57 +0.54%
5.54 5.56 +0.36%
5.54 5.58 +0.72%
5.51 5.58 +1.25%
5.54 5.57 +0.54%
Aumento médio total na velocidade de std :: copy over memcpy: 0,72%
Em resumo, não parece haver uma penalidade de desempenho pelo uso std::copy
. De fato, parece haver um ganho de desempenho.
Explicação dos resultados
Então, por que pode std::copy
dar um impulso no desempenho?
Primeiro, eu não esperaria que fosse mais lento para qualquer implementação, desde que a otimização do inlining estivesse ativada. Todos os compiladores se alinham agressivamente; é possivelmente a otimização mais importante porque permite muitas outras otimizações. std::copy
É possível (e suspeito que todas as implementações do mundo real) detectar que os argumentos são trivialmente copiáveis e que a memória é organizada em seqüência. Isso significa que, na pior das hipóteses, quando memcpy
é legal, não std::copy
deve ter desempenho pior. A implementação trivial std::copy
disso adia memcpy
deve atender aos critérios do seu compilador de "sempre alinhar isso ao otimizar velocidade ou tamanho".
No entanto, std::copy
também mantém mais informações. Quando você liga std::copy
, a função mantém os tipos intactos. memcpy
opera void *
, que descarta quase todas as informações úteis. Por exemplo, se eu passar uma matriz de std::uint64_t
, o compilador ou o implementador da biblioteca poderá tirar proveito do alinhamento de 64 bits com std::copy
, mas pode ser mais difícil fazê-lo memcpy
. Muitas implementações de algoritmos como esse funcionam primeiro trabalhando na parte não alinhada no início do intervalo, depois na parte alinhada e depois na parte não alinhada no final. Se tudo estiver garantido para estar alinhado, o código se tornará mais simples e rápido, e mais fácil para o preditor de ramificação do seu processador se corrigir.
Otimização prematura?
std::copy
está em uma posição interessante. Espero que nunca seja mais lento memcpy
e às vezes mais rápido com qualquer compilador de otimização moderno. Além disso, tudo o que você puder memcpy
, você pode std::copy
. memcpy
não permite nenhuma sobreposição nos buffers, enquanto os std::copy
suportes se sobrepõem em uma direção (com std::copy_backward
para a outra direção de sobreposição). memcpy
só funciona em ponteiros, std::copy
funciona em qualquer iteradores ( std::map
, std::vector
, std::deque
, ou meu próprio tipo personalizado). Em outras palavras, você deve usar apenas std::copy
quando precisar copiar blocos de dados.
char
pode ser assinado ou não, dependendo da implementação. Se o número de bytes puder ser> = 128, useunsigned char
para suas matrizes de bytes. (O(int *)
elenco seria mais seguro, como(unsigned int *)
, também.)