web-dev-qa-db-fra.com

Dans quels cas dois-je utiliser memcpy sur des opérateurs standard en C ++?

Quand puis-je obtenir de meilleures performances en utilisant memcpy ou comment puis-je en bénéficier? Par exemple:

float a[3]; float b[3];

est le code:

memcpy(a, b, 3*sizeof(float));

plus rapide que celui-ci?

a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
33
Patryk Czachurski

L'efficacité ne devrait pas être votre préoccupation.
Écrivez un code propre et maintenable.

Cela me dérange que tant de réponses indiquent que le memcpy () est inefficace. Il est conçu pour être le moyen le plus efficace de copier des blocs de mémoire (pour les programmes C).

J'ai donc écrit ce qui suit comme test:

#include <algorithm>

extern float a[3];
extern float b[3];
extern void base();

int main()
{
    base();

#if defined(M1)
    a[0] = b[0];
    a[1] = b[1];
    a[2] = b[2];
#Elif defined(M2)
    memcpy(a, b, 3*sizeof(float));    
#Elif defined(M3)
    std::copy(&a[0], &a[3], &b[0]);
 #endif

    base();
}

Ensuite, comparer le code produit:

g++ -O3 -S xr.cpp -o s0.s
g++ -O3 -S xr.cpp -o s1.s -DM1
g++ -O3 -S xr.cpp -o s2.s -DM2
g++ -O3 -S xr.cpp -o s3.s -DM3

echo "=======" >  D
diff s0.s s1.s >> D
echo "=======" >> D
diff s0.s s2.s >> D
echo "=======" >> D
diff s0.s s3.s >> D

Cela s'est traduit par: (commentaires ajoutés à la main)

=======   // Copy by hand
10a11,18
>   movq    _a@GOTPCREL(%rip), %rcx
>   movq    _b@GOTPCREL(%rip), %rdx
>   movl    (%rdx), %eax
>   movl    %eax, (%rcx)
>   movl    4(%rdx), %eax
>   movl    %eax, 4(%rcx)
>   movl    8(%rdx), %eax
>   movl    %eax, 8(%rcx)

=======    // memcpy()
10a11,16
>   movq    _a@GOTPCREL(%rip), %rcx
>   movq    _b@GOTPCREL(%rip), %rdx
>   movq    (%rdx), %rax
>   movq    %rax, (%rcx)
>   movl    8(%rdx), %eax
>   movl    %eax, 8(%rcx)

=======    // std::copy()
10a11,14
>   movq    _a@GOTPCREL(%rip), %rsi
>   movl    $12, %edx
>   movq    _b@GOTPCREL(%rip), %rdi
>   call    _memmove

Ajout de résultats de synchronisation pour exécuter ce qui précède dans une boucle de 1000000000.

   g++ -c -O3 -DM1 X.cpp
   g++ -O3 X.o base.o -o m1
   g++ -c -O3 -DM2 X.cpp
   g++ -O3 X.o base.o -o m2
   g++ -c -O3 -DM3 X.cpp
   g++ -O3 X.o base.o -o m3
   time ./m1

   real 0m2.486s
   user 0m2.478s
   sys  0m0.005s
   time ./m2

   real 0m1.859s
   user 0m1.853s
   sys  0m0.004s
   time ./m3

   real 0m1.858s
   user 0m1.851s
   sys  0m0.006s
53
Martin York

Vous ne pouvez utiliser memcpy que si les objets que vous copiez n'ont pas de constructeurs explicites, de même que leurs membres (appelés POD, "Plain Old Data"). Il est donc correct d'appeler memcpy pour float, mais c'est faux pour, par exemple, std::string.

Mais une partie du travail a déjà été fait pour vous: std::copy De <algorithm> Est spécialisé pour les types intégrés (et éventuellement pour tous les autres types de POD - dépend de l'implémentation de STL). Ainsi, l'écriture de std::copy(a, a + 3, b) est aussi rapide (après optimisation du compilateur) que memcpy, mais est moins sujette aux erreurs.

14
crazylammer

Les compilateurs optimisent spécifiquement les appels memcpy, du moins clang & gcc le fait. Vous devriez donc le préférer où vous le pouvez.

10
ismail

Utilisez std::copy(). En tant que fichier d'en-tête pour g++ Remarques:

Cette fonction en ligne se résumera à un appel à @c memmove chaque fois que possible.

Probablement, Visual Studio n'est pas très différent. Suivez la voie normale et optimisez une fois que vous avez connaissance d'un goulot d'étranglement. Dans le cas d'une simple copie, le compilateur est probablement déjà en train de s'optimiser pour vous.

6
Thanatos

N'allez pas pour des micro-optimisations prématurées telles que l'utilisation de memcpy comme ça. L'utilisation de l'affectation est plus claire et moins sujette aux erreurs et tout compilateur décent générera un code suffisamment efficace. Si, et seulement si, vous avez profilé le code et trouvé que les affectations sont un goulot d'étranglement important, vous pouvez envisager une sorte de micro-optimisation, mais en général, vous devriez toujours écrire du code clair et robuste en premier lieu.

5
Paul R

Les avantages de memcpy? Probablement lisibilité. Sinon, vous devrez soit effectuer un certain nombre d'affectations, soit avoir une boucle for pour la copie, qui ne sont ni aussi simples ni aussi claires que simplement faire memcpy (bien sûr, tant que vos types sont simples et ne nécessitent pas de construction/destruction).

En outre, memcpy est généralement relativement optimisé pour des plates-formes spécifiques, au point qu'il ne sera pas beaucoup plus lent qu'une simple affectation, et peut même être plus rapide.

4
Jamie

Soi-disant, comme l'a dit Nawaz, la version d'affectation devrait être plus rapide sur la plupart des plateformes. C'est parce que memcpy() copiera octet par octet tandis que la deuxième version pourrait copier 4 octets à la fois.

Comme c'est toujours le cas, vous devez toujours profiler les applications pour être sûr que ce que vous attendez d'être le goulot d'étranglement correspond à la réalité.

Modifier
La même chose s'applique au tableau dynamique. Puisque vous mentionnez C++, vous devez utiliser l'algorithme std::copy() dans ce cas.

Modifier
Ceci est une sortie de code pour Windows XP avec GCC 4.5.0, compilé avec l'indicateur -O3:

extern "C" void cpy(float* d, float* s, size_t n)
{
    memcpy(d, s, sizeof(float)*n);
}

J'ai fait cette fonction car OP a également spécifié des tableaux dynamiques.

L'assemblage de sortie est le suivant:

_cpy:
LFB393:
    pushl   %ebp
LCFI0:
    movl    %esp, %ebp
LCFI1:
    pushl   %edi
LCFI2:
    pushl   %esi
LCFI3:
    movl    8(%ebp), %eax
    movl    12(%ebp), %esi
    movl    16(%ebp), %ecx
    sall    $2, %ecx
    movl    %eax, %edi
    rep movsb
    popl    %esi
LCFI4:
    popl    %edi
LCFI5:
    leave
LCFI6:
    ret

bien sûr, je suppose que tous les experts ici savent ce que rep movsb veux dire.

Ceci est la version d'affectation:

extern "C" void cpy2(float* d, float* s, size_t n)
{
    while (n > 0) {
        d[n] = s[n];
        n--;
    }
}

qui donne le code suivant:

_cpy2:
LFB394:
    pushl   %ebp
LCFI7:
    movl    %esp, %ebp
LCFI8:
    pushl   %ebx
LCFI9:
    movl    8(%ebp), %ebx
    movl    12(%ebp), %ecx
    movl    16(%ebp), %eax
    testl   %eax, %eax
    je  L2
    .p2align 2,,3
L5:
    movl    (%ecx,%eax,4), %edx
    movl    %edx, (%ebx,%eax,4)
    decl    %eax
    jne L5
L2:
    popl    %ebx
LCFI10:
    leave
LCFI11:
    ret

Qui déplace 4 octets à la fois.

0
Simone