É melhor usar memcpy
como mostrado abaixo ou é melhor usar std::copy()
em termos de desempenho? Por quê?
char *bits = NULL;
...
bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
cout << "ERROR Not enough memory.\n";
exit(1);
}
memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);
c++
performance
optimization
user576670
fonte
fonte
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.)std::vector<char>
? Ou desde que você dizbits
,std::bitset
?(int*) copyMe->bits[0]
faz?int
o tamanho, mas isso parece uma receita para um desastre definido pela implementação, como tantas outras coisas aqui.(int *)
elenco é apenas um comportamento indefinido puro, não definido pela implementação. Tentar fazer punções de tipo por meio de um elenco viola regras estritas de alias e, portanto, é totalmente indefinido pelo Padrão. (Além disso, em C ++, embora não seja C, você também não pode digitar trocadilhos por meio de umunion
.) Praticamente a única exceção é se você estiver convertendo para uma variante dechar*
, mas a permissão não é simétrica.Respostas:
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 foistd::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 astd::copy
versão. Meu código aproveita a captura de dados no maior número possível de partes (muitas outras implementações operam comchar
/char *
, enquanto eu opero comT
/T *
(ondeT
é 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
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
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 chamoustd::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
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, quandomemcpy
é legal, nãostd::copy
deve ter desempenho pior. A implementação trivialstd::copy
disso adiamemcpy
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ê ligastd::copy
, a função mantém os tipos intactos.memcpy
operavoid *
, que descarta quase todas as informações úteis. Por exemplo, se eu passar uma matriz destd::uint64_t
, o compilador ou o implementador da biblioteca poderá tirar proveito do alinhamento de 64 bits comstd::copy
, mas pode ser mais difícil fazê-lomemcpy
. 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 lentomemcpy
e às vezes mais rápido com qualquer compilador de otimização moderno. Além disso, tudo o que você pudermemcpy
, você podestd::copy
.memcpy
não permite nenhuma sobreposição nos buffers, enquanto osstd::copy
suportes se sobrepõem em uma direção (comstd::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 apenasstd::copy
quando precisar copiar blocos de dados.fonte
std::copy
seja 2,99% ou 0,72% ou -0,11% mais rápido quememcpy
esses tempos para o programa inteiro ser executado. No entanto, geralmente considero que referências no código real são mais úteis do que referências no código falso. Todo o meu programa teve essa mudança na velocidade de execução. Os efeitos reais de apenas os dois esquemas de cópia terão maiores diferenças do que as mostradas aqui quando tomadas isoladamente, mas isso mostra que eles podem ter diferenças mensuráveis no código real.memcpy
estd::copy
tem implementações diferentes, portanto, em alguns casos, o compilador otimiza o código circundante e o código de cópia de memória real como uma parte integrante do código. Em outras palavras, às vezes um é melhor que outro e até em outras palavras, decidir qual usar é uma otimização prematura ou até estúpida, porque em todas as situações você precisa fazer novas pesquisas e, além do mais, programas geralmente estão sendo desenvolvidos; algumas pequenas alterações na vantagem da função sobre outras podem ser perdidas.std::copy
é uma função em linha trivial que apenas chamamemcpy
quando é legal. O alinhamento básico eliminaria qualquer diferença negativa de desempenho. Vou atualizar o post com uma explicação de por que std :: copy pode ser mais rápido.Todos os compiladores que eu conheço substituirão um simples
std::copy
por ummemcpy
quando for apropriado, ou melhor ainda, vetorizar a cópia para que ela seja ainda mais rápida que amemcpy
.De qualquer forma: perfil e descubra você mesmo. Compiladores diferentes farão coisas diferentes, e é bem possível que não faça exatamente o que você pede.
Veja esta apresentação sobre otimizações do compilador (pdf).
Aqui está o que o GCC faz para um simples
std::copy
tipo de POD.Aqui está a desmontagem (apenas com
-O
otimização), mostrando a chamada paramemmove
:Se você alterar a assinatura da função para
então
memmove
torna-se ummemcpy
para uma ligeira melhoria de desempenho. Observe quememcpy
ele próprio será fortemente vetorizado.fonte
memmove
não deve ser mais rápido. Em vez disso, deve ser mais lento, porque deve levar em consideração a possibilidade de os dois intervalos de dados se sobreporem. Eu acho questd::copy
permite a sobreposição de dados, e por isso tem que ligarmemmove
.memcpy
. Isso me leva a acreditar que o GCC verifica se há sobreposição de memória.std::copy
permite sobreposição em uma direção, mas não na outra. O início da saída não pode estar dentro da faixa de entrada, mas o início da entrada pode estar dentro da faixa de saída. Isso é um pouco estranho, porque a ordem das atribuições é definida e uma chamada pode ser UB mesmo que o efeito dessas atribuições, nessa ordem, seja definido. Mas suponho que a restrição permita otimizações de vetorização.Sempre use
std::copy
porquememcpy
está limitado apenas a estruturas de POD no estilo C, e o compilador provavelmente substituirá as chamadasstd::copy
pormemcpy
se os destinos forem de fato POD.Além disso,
std::copy
pode ser usado com muitos tipos de iteradores, não apenas com ponteiros.std::copy
é mais flexível, sem perda de desempenho e é o vencedor.fonte
std::copy(container.begin(), container.end(), destination);
copiará o conteúdo decontainer
(tudo entrebegin
eend
) no buffer indicado pordestination
.std::copy
não requer travessuras como&*container.begin()
ou&container.back() + 1
.Em teoria,
memcpy
pode ter uma vantagem de desempenho leve , imperceptível , infinitesimal , apenas porque não possui os mesmos requisitos questd::copy
. Na página do manual dememcpy
:Em outras palavras,
memcpy
pode ignorar a possibilidade de sobreposição de dados. (Passar matrizes sobrepostas paramemcpy
é um comportamento indefinido.) Portanto,memcpy
não é necessário verificar explicitamente essa condição,std::copy
pois pode ser usado desde que oOutputIterator
parâmetro não esteja no intervalo de origem. Observe que isso não é o mesmo que dizer que o intervalo de origem e o destino não podem se sobrepor.Portanto, como
std::copy
possui requisitos um pouco diferentes, em teoria, ele deve ser um pouco (com ênfase extrema em um pouco ) mais lento, pois provavelmente verificará a sobreposição de matrizes C ou delegará a cópia de matrizes C paramemmove
, que precisa executar o Verifica. Mas, na prática, você (e a maioria dos criadores de perfil) provavelmente nem detectará nenhuma diferença.Obviamente, se você não estiver trabalhando com PODs , não poderá usá-lo de
memcpy
qualquer maneira.fonte
std::copy<char>
. Masstd::copy<int>
pode assumir que suas entradas estão alinhadas int. Isso fará uma diferença muito maior, porque afeta todos os elementos. A sobreposição é uma verificação única.memcpy
eu já vi verificar alinhamento e tentar copiar palavras em vez de byte a byte.memcpy
interface, perde as informações de alinhamento. Portanto,memcpy
precisa fazer verificações de alinhamento no tempo de execução para lidar com inícios e fins desalinhados. Esses cheques podem ser baratos, mas não são gratuitos. Considerando questd::copy
pode evitar essas verificações e vetorizar. Além disso, o compilador pode provar que as matrizes de origem e destino não se sobrepõem e novamente se vetorizam sem que o usuário precise escolher entrememcpy
ememmove
.Minha regra é simples. Se você estiver usando C ++, prefira bibliotecas C ++ e não C :)
fonte
std::end(c_arr)
vez dec_arr + i_hope_this_is_the_right_number_of elements
é mais seguro? e talvez mais importante, mais claro. E isso seria o ponto Enfatizo, neste caso específico:std::copy()
é mais idiomática, mais sustentável se os tipos de iteradores muda mais tarde, leva a sintaxe mais clara, etc.std::copy
é mais seguro porque copia corretamente os dados passados, caso não sejam do tipo POD.memcpy
felizmente copiará umstd::string
objeto para um novo byte byte de representação.Apenas uma pequena adição: a diferença de velocidade entre
memcpy()
estd::copy()
pode variar bastante, dependendo se as otimizações estão ativadas ou desativadas. Com o g ++ 6.2.0 e sem otimizaçõesmemcpy()
, ganha claramente:Quando as otimizações estão ativadas (
-O3
), tudo parece praticamente o mesmo novamente:Quanto maior a matriz, menos perceptível o efeito fica, mas mesmo assim
N=1000
memcpy()
é duas vezes mais rápido quando as otimizações não estão ativadas.Código fonte (requer o Google Benchmark):
fonte
Se você realmente precisa do desempenho máximo de cópia (o que talvez não seja), use nenhum deles .
Há um monte de que pode ser feito para cópia de memória otimizar - ainda mais se você estiver disposto a usar vários segmentos / núcleos para ele. Veja, por exemplo:
O que está faltando / abaixo do ideal nessa implementação memcpy?
a pergunta e algumas das respostas sugeriram implementações ou links para implementações.
fonte
A criação de perfil mostra essa afirmação:
std::copy()
é sempre tão rápida quantomemcpy()
ou mais rápida é falsa.Meu sistema:
O código (idioma: c ++):
O Alerta Vermelho apontou que o código usa memcpy de matriz para matriz e std :: copy de matriz para vetor. Esse poderia ser um motivo para memcpy mais rápido.
Uma vez que existe
v.reserve (sizeof (arr1));
não deve haver diferença na cópia para vetor ou matriz.
O código é fixo para usar a matriz nos dois casos. memcpy ainda mais rápido:
fonte
std::copy
de um vetor para um array de alguma formamemcpy
levou quase o dobro do tempo? Esses dados são altamente suspeitos. Compilei seu código usando gcc com -O3, e o assembly gerado é o mesmo para os dois loops. Portanto, qualquer diferença de tempo observada em sua máquina é apenas incidental.