Comme nous le savons, dans un ordinateur de mots multi-octets tel que x86 / x86_64, il est plus efficace de copier / déplacer une grande partie de la mémoire mot par mot (4 ou 8 octets par pas), que de le faire octet par octet.
Je suis curieux de savoir de quelle manière strncpy / memcpy / memmove ferait les choses, et comment gèrent-ils l'alignement des mots de mémoire.
char buf_A[8], buf_B[8]; // I often want to code as this *(double*)buf_A = *(double*)buf_B; //in stead of this strcpy(buf_A, buf_B); // but it worsen the readability of my codes.
6 Réponses :
De cpp-reference :
Copie count octets de l'objet pointé par src vers l'objet pointé par dest. Les deux objets sont réinterprétés comme des tableaux de caractères non signés.
std :: memcpy est censé être la routine de bibliothèque la plus rapide pour la copie de mémoire à mémoire. Il est généralement plus efficace que std :: strcpy, qui doit analyser les données qu'il copie ou std :: memmove, qui doit prendre des précautions pour gérer les entrées qui se chevauchent.
Plusieurs compilateurs C ++ transforment les boucles de copie mémoire appropriées en appels std :: memcpy.
Là où l'aliasing strict interdit d'examiner la même mémoire que des valeurs de deux types différents, std :: memcpy peut être utilisé pour convertir les valeurs.
Cela devrait donc être le moyen le plus rapide de copier des données. Sachez cependant qu'il existe plusieurs cas où le comportement n'est pas défini:
Si les objets se chevauchent, le comportement n'est pas défini.
Si dest ou src est un pointeur nul, le comportement n'est pas défini, même si count vaut zéro.
Si les objets se chevauchent potentiellement ou ne sont pas TriviallyCopyable, le comportement de memcpy n'est pas spécifié et peut être indéfini.
Je vous remercie! Mais je veux toujours savoir de quelle manière std :: memcpy fait les choses. Octet par octet, ou mot par mot?
Vous copiez un certain nombre d'octets. Il n'y a aucune garantie de mise en œuvre.
Est-ce que strcpy / strncpy copie les données octet par octet ou d'une autre manière efficace?
Le standard C ++ ni C ne spécifie pas comment strcpy / strncpy est implémenté exactement. Ils ne décrivent que le comportement.
Il existe plusieurs implémentations de bibliothèques standard et chacune choisit comment implémenter ses fonctions. Il est possible d'implémenter les deux en utilisant memcpy. Les standards ne décrivent pas exactement l'implémentation de memcpy non plus, et l'existence de plusieurs implémentations s'y applique tout aussi bien.
memcpy peut être implémentée en tirant parti de la copie complète des mots. Un court pseudocode expliquant comment memcpy pourrait être implémenté:
if len >= 2 * word size copy bytes until destination pointer is aligned to word boundary if len >= page size copy entire pages using virtual address manipulation copy entire words copy the trailing bytes that are not aligned to word boundaryPour découvrir comment une implémentation de bibliothèque standard particulière implémente strcpy / strncpy / memcpy, vous pouvez lisez le code source de la bibliothèque standard - si vous y avez accès.
Encore plus, lorsque la longueur est connue au moment de la compilation, le compilateur peut même choisir de ne pas utiliser la bibliothèque memcpy, mais à la place la copie en ligne. Si votre compilateur a intégré des définitions pour les fonctions de bibliothèque standard, vous pouvez le découvrir dans la documentation du compilateur respectif.
En général, vous n'avez pas à trop réfléchir à la manière dont memcpy
ou d'autres fonctions similaires sont implémentées. Vous devez supposer qu'ils sont efficaces à moins que votre profilage ne prouve que vous avez tort.
En pratique, il est en effet bien optimisé. Voir par exemple le code de test suivant:
test(char (&) [8], char (&) [8]): mov rax, QWORD PTR [rsi] mov QWORD PTR [rdi], rax ret
En le compilant avec g ++ 7.3.0 avec la commande g ++ test.cpp -O3 -S -masm = intel
nous peut voir le code d'assemblage suivant:
#include <cstring> void test(char (&a)[8], char (&b)[8]) { std::memcpy(&a,&b,sizeof a); }
Comme vous pouvez le voir, la copie est non seulement incorporée, mais également réduite en une seule lecture et écriture de 8 octets.
Dans ce cas, vous pouvez préférer utiliser memcpy
car c'est l'équivalent de * (double *) buf_A = * (double *) buf_B;
sans comportement indéfini.
Vous ne devez pas vous soucier d'appeler memcpy
car par défaut le compilateur suppose qu'un appel à memcpy
a la signification définie dans la bibliothèque c. Ainsi, selon le type de l'argument et / ou la connaissance de la taille de la copie au moment de la compilation, le compilateur peut choisir de ne pas appeler la fonction de bibliothèque c et d'intégrer une stratégie de copie mémoire plus adaptée. Sur gcc, vous pouvez désactiver ce comportement avec l'option du compilateur -fno-builtin
: demo a>.
Le remplacement de l'appel memcpy par le compilateur est souhaité car memcpy vérifiera la taille et l'alignement des pointeurs pour utiliser la stratégie de copie mémoire la plus efficace (il peut commencer à copier aussi petits blocs que char par char en très gros blocs en utilisant l'instruction AVX512 par exemple). Ces vérifications et que ce soit l'appel à memcpy coûtent.
Aussi, si vous recherchez l'efficacité, vous devriez vous préoccuper de l'alignement de la mémoire. Vous voudrez peut-être déclarer l'alignement de votre tampon:
alignas(8) char buf_A[8];
Cela dépend du compilateur que vous utilisez et de la bibliothèque d'exécution C que vous utilisez. Dans la plupart des cas, les fonctions string.h telles que memcmp
, memcpy
, strcpu
, memset
, etc. implémentées en utilisant l'assemblage dans le CPU manière optimisée.
Vous pouvez trouver les implémentations GNU libc de ces fonctions pour l'AMD64 architecture . Comme vous pouvez le voir, il peut utiliser des instructions SSE ou AVX pour copier 128 et 512 bits par itération. Microsoft regroupe également le code source de leur CRT avec Visual Studio (les mêmes approches généralement, les boucles MMX, SSE, AVX sont prises en charge).
Le compilateur utilise également une optimisation spéciale pour ces fonctions, GCC les appelle builtins un> autre compilateur les appelle intrinsèques. C'est à dire. le compilateur peut choisir - appeler une fonction de bibliothèque, ou générer un code d'assemblage spécifique au processeur optimal pour le contexte actuel. Par exemple, lorsque l'argument N
de memcpy
est constant, c'est-à-dire que le compilateur memcpy (dst, src, 128)
peut générer un code d'assemblage en ligne (quelque chose comme < code> mov 16, rcx cls rep stosq ), et quand il s'agit d'une variable ie memcpy (dst, src, bytes)
- le compilateur peut insérer un appel à la fonction de bibliothèque (quelque chose comme appeler _memcpy
)
Je pense que tous les avis et conseils sur cette page sont raisonnables, mais je décide d'essayer une petite expérience.
À ma grande surprise, la méthode la plus rapide n'est pas celle que nous attendions théoriquement.
J'ai essayé du code comme suit.
#include <cstring> #include <iostream> #include <string> #include <chrono> using std::string; using std::chrono::system_clock; inline void mycopy( double* a, double* b, size_t s ) { while ( s > 0 ) { *a++ = *b++; --s; } }; // to make sure that every bits have been changed bool assertAllTrue( unsigned char* a, size_t s ) { unsigned char v = 0xFF; while ( s > 0 ) { v &= *a++; --s; } return v == 0xFF; }; int main( int argc, char** argv ) { alignas( 16 ) char bufA[512], bufB[512]; memset( bufB, 0xFF, 512 ); // to prevent strncpy from stoping prematurely system_clock::time_point startT; memset( bufA, 0, sizeof( bufA ) ); startT = system_clock::now(); for ( int i = 0; i < 1024 * 1024; ++i ) strncpy( bufA, bufB, sizeof( bufA ) ); std::cout << "strncpy:" << ( system_clock::now() - startT ).count() << ", AllTrue:" << std::boolalpha << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) ) << std::endl; memset( bufA, 0, sizeof( bufA ) ); startT = system_clock::now(); for ( int i = 0; i < 1024 * 1024; ++i ) memcpy( bufA, bufB, sizeof( bufA ) ); std::cout << "memcpy:" << ( system_clock::now() - startT ).count() << ", AllTrue:" << std::boolalpha << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) ) << std::endl; memset( bufA, 0, sizeof( bufA ) ); startT = system_clock::now(); for ( int i = 0; i < 1024 * 1024; ++i ) memmove( bufA, bufB, sizeof( bufA ) ); std::cout << "memmove:" << ( system_clock::now() - startT ).count() << ", AllTrue:" << std::boolalpha << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) ) << std::endl; memset( bufA, 0, sizeof( bufA ) ); startT = system_clock::now(); for ( int i = 0; i < 1024 * 1024; ++i ) mycopy( ( double* )bufA, ( double* )bufB, sizeof( bufA ) / sizeof( double ) ); std::cout << "mycopy:" << ( system_clock::now() - startT ).count() << ", AllTrue:" << std::boolalpha << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) ) << std::endl; return EXIT_SUCCESS; }
Le résultat (l'un des nombreux résultats similaires):
strncpy: 52840919, AllTrue: true
memcpy: 57630499, AllTrue: true
memmove: 57536472, AllTrue: true
mycopie: 57577863, AllTrue: true
Cela ressemble à:
Est-ce drôle?
Maintenant, je peux utiliser strncpy sans aucun souci!
memcpy, memmove et ma propre méthode ont un résultat similaire; - parce que le compilateur a remplacé votre méthode par l'appel de la bibliothèque à memcpy pendant l'optimisation. Si vous désactivez l'optimisation, vous verrez la différence.
Le fonctionnement de
strcpy
et de ses amis dépend totalement de l'implémentation. Habituellement, ils font simplement le travail de manière efficace. Oh oui etstrcpy
ne peuvent être utilisés que pour copier des chaînes terminées par NUL (lisez le chapitre traitant des chaînes dans votre manuel C). Dans votre cas, vous devez utilisermemcpy
.Sur la plupart des plates-formes, le code source de
memcpy
etc. est inclus, regardez-les, mais le code peut être trop surprenant. Ils sont également souvent écrits à la main en langage d'assemblage.Merci, Jabberwocky! J'utilise gcc 8.2.0 dans un fichier x86_64. Pourriez-vous s'il vous plaît me dire où puis-je trouver la source du memcpy / strcpy? J'ai toute la source de gcc, mais je ne sais pas y nager. C'est vraiment un océan.
Sauf si vous avez une bonne raison de penser le contraire, vous devez supposer que l'implémenteur de bibliothèque standard connaît mieux que vous le matériel cible et les exigences de la bibliothèque. Ce n'est pas une réprobation, juste un commentaire sur l'expertise.