Vaut-il mieux utiliser memcpy
comme indiqué ci-dessous ou est-il préférable d'utiliser std::copy()
en termes de performances? Pourquoi?
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
utilisateur576670
la source
la source
char
peut être signé ou non signé, selon l'implémentation. Si le nombre d'octets peut être> = 128, utilisezunsigned char
pour vos tableaux d'octets. (Le(int *)
casting serait aussi plus sûr(unsigned int *)
.)std::vector<char>
? Ou puisque vous ditesbits
,std::bitset
?(int*) copyMe->bits[0]
fait?int
définition de sa taille, mais cela semble être une recette pour un désastre défini par l'implémentation, comme tant d'autres choses ici.(int *)
cast est juste un comportement indéfini pur, pas défini par l'implémentation. Essayer de faire du poinçonnage de type via une distribution enfreint les règles strictes d'aliasing et est donc totalement indéfini par le Standard. (De plus, en C ++ mais pas en C, vous ne pouvez pas non plus taper un jeu de mots via aunion
.) La seule exception est si vous convertissez en une variante dechar*
, mais l'allocation n'est pas symétrique.Réponses:
Je vais aller à l'encontre de la sagesse générale ici qui
std::copy
entraînera une légère perte de performance presque imperceptible. Je viens de faire un test et j'ai trouvé que c'était faux: j'ai remarqué une différence de performance. Cependant, le gagnant étaitstd::copy
.J'ai écrit une implémentation C ++ SHA-2. Dans mon test, je hache 5 chaînes en utilisant les quatre versions SHA-2 (224, 256, 384, 512) et je boucle 300 fois. Je mesure les temps en utilisant Boost.timer. Ce compteur de 300 boucles suffit à stabiliser complètement mes résultats. J'ai effectué le test 5 fois chacun, en alternant entre la
memcpy
version et lastd::copy
version. Mon code tire parti de la saisie de données en aussi gros morceaux que possible (de nombreuses autres implémentations fonctionnent avecchar
/char *
, alors que j'opère avecT
/T *
(oùT
est le plus grand type dans l'implémentation de l'utilisateur qui a un comportement de débordement correct), donc un accès mémoire rapide sur le les plus grands types possibles sont essentiels aux performances de mon algorithme. Voici mes résultats:Temps (en secondes) pour terminer l'exécution des tests SHA-2
Augmentation moyenne totale de la vitesse de std :: copy sur memcpy: 2,99%
Mon compilateur est gcc 4.6.3 sur Fedora 16 x86_64. Mes indicateurs d'optimisation sont
-Ofast -march=native -funsafe-loop-optimizations
.Code pour mes implémentations SHA-2.
J'ai décidé de tester également mon implémentation MD5. Les résultats étaient beaucoup moins stables, j'ai donc décidé de faire 10 courses. Cependant, après mes premières tentatives, j'ai obtenu des résultats qui variaient énormément d'une exécution à l'autre, donc je suppose qu'il y avait une sorte d'activité du système d'exploitation. J'ai décidé de recommencer.
Mêmes paramètres et indicateurs du compilateur. Il n'y a qu'une seule version de MD5, et c'est plus rapide que SHA-2, j'ai donc fait 3000 boucles sur un ensemble similaire de 5 chaînes de test.
Voici mes 10 derniers résultats:
Temps (en secondes) pour terminer l'exécution des tests MD5
Diminution moyenne totale de la vitesse de std :: copy sur memcpy: 0,11%
Code pour mon implémentation MD5
Ces résultats suggèrent qu'il existe une certaine optimisation que std :: copy utilisée dans mes tests SHA-2 qui
std::copy
n'a pas pu être utilisée dans mes tests MD5. Dans les tests SHA-2, les deux tableaux ont été créés dans la même fonction qui a appeléstd::copy
/memcpy
. Dans mes tests MD5, l'un des tableaux a été transmis à la fonction en tant que paramètre de fonction.J'ai fait un peu plus de tests pour voir ce que je pouvais faire pour
std::copy
accélérer à nouveau. La réponse s'est avérée simple: activez l'optimisation du temps de liaison. Voici mes résultats avec LTO activé (option -flto dans gcc):Temps (en secondes) pour terminer l'exécution des tests MD5 avec -flto
Augmentation moyenne totale de la vitesse de std :: copy sur memcpy: 0,72%
En résumé, il ne semble pas y avoir de pénalité de performance pour l'utilisation
std::copy
. En fait, il semble y avoir un gain de performance.Explication des résultats
Alors, pourquoi pourrait-il
std::copy
augmenter les performances?Premièrement, je ne m'attendrais pas à ce qu'il soit plus lent pour une implémentation, tant que l'optimisation de l'inlining est activée. Tous les compilateurs en ligne de manière agressive; c'est peut-être l'optimisation la plus importante car elle permet de nombreuses autres optimisations.
std::copy
peut (et je soupçonne que toutes les implémentations du monde réel le font) détecter que les arguments sont trivialement copiables et que la mémoire est disposée séquentiellement. Cela signifie que dans le pire des cas, quandmemcpy
c'est légal,std::copy
ne devrait pas être pire. L'implémentation triviale destd::copy
ce reportmemcpy
devrait répondre aux critères de votre compilateur de "toujours en ligne lors de l'optimisation de la vitesse ou de la taille".Cependant,
std::copy
conserve également plus de ses informations. Lorsque vous appelezstd::copy
, la fonction conserve les types intacts.memcpy
fonctionnevoid *
, ce qui supprime presque toutes les informations utiles. Par exemple, si je passe dans un tableau destd::uint64_t
, le compilateur ou l'implémenteur de la bibliothèque peut être en mesure de profiter de l'alignement 64 bits avecstd::copy
, mais il peut être plus difficile de le faire avecmemcpy
. De nombreuses implémentations d'algorithmes comme celui-ci fonctionnent en travaillant d'abord sur la partie non alignée au début de la plage, puis sur la partie alignée, puis sur la partie non alignée à la fin. S'il est garanti que tout est aligné, le code devient plus simple et plus rapide, et plus facile pour le prédicteur de branche de votre processeur à être correct.Optimisation prématurée?
std::copy
est dans une position intéressante. Je m'attends à ce qu'il ne soit jamais plus lentmemcpy
et parfois plus rapide avec n'importe quel compilateur d'optimisation moderne. De plus, tout ce que vous pouvezmemcpy
, vous le pouvezstd::copy
.memcpy
ne permet aucun chevauchement dans les tampons, alors que lesstd::copy
supports se chevauchent dans un sens (avecstd::copy_backward
pour l'autre sens de recouvrement).memcpy
ne fonctionne que sur des pointeurs,std::copy
fonctionne sur tous les itérateurs (std::map
,std::vector
,std::deque
, ou mon propre type personnalisé). En d'autres termes, vous ne devriez l'utiliser questd::copy
lorsque vous avez besoin de copier des morceaux de données.la source
std::copy
c'est 2,99% ou 0,72% ou -0,11% plus rapide quememcpy
, ces temps sont pour tout le programme à exécuter. Cependant, je pense généralement que les benchmarks dans du code réel sont plus utiles que les benchmarks dans du faux code. Tout mon programme a eu ce changement de vitesse d'exécution. Les effets réels des deux schémas de copie seulement auront des différences plus importantes que celles indiquées ici lorsqu'ils sont pris isolément, mais cela montre qu'ils peuvent avoir des différences mesurables dans le code réel.memcpy
etstd::copy
a différentes implémentations, donc dans certains cas, le compilateur optimise le code environnant et le code de copie de mémoire réel comme un morceau de code intégral. En d'autres termes, parfois l' un est meilleur que l'autre et même en d'autres termes, décider lequel utiliser est une optimisation prématurée ou même stupide, car dans chaque situation, vous devez faire de nouvelles recherches et, de plus, des programmes sont généralement en cours de développement, donc après certains changements mineurs peuvent perdre l'avantage de la fonction sur d'autres.std::copy
c'est une fonction en ligne triviale qui n'appelle quememcpy
quand c'est légal. L'inlining de base éliminerait toute différence de performance négative. Je vais mettre à jour le message avec une petite explication de la raison pour laquelle std :: copy pourrait être plus rapide.Tous les compilateurs que je connais remplaceront un simple
std::copy
par unmemcpy
quand c'est approprié, ou encore mieux, vectoriseront la copie pour qu'elle soit encore plus rapide qu'unmemcpy
.Dans tous les cas: profilez et découvrez vous-même. Différents compilateurs feront des choses différentes, et il est fort possible qu'il ne fasse pas exactement ce que vous demandez.
Voir cette présentation sur les optimisations du compilateur (pdf).
Voici ce que fait GCC pour un simple
std::copy
de type POD.Voici le démontage (avec seulement l'
-O
optimisation), montrant l'appel àmemmove
:Si vous modifiez la signature de la fonction en
puis le
memmove
devient unmemcpy
pour une légère amélioration des performances. Notez quememcpy
lui - même sera fortement vectorisé.la source
memmove
ne devrait pas être plus rapide, mais plutôt plus lent, car il doit prendre en compte la possibilité que les deux plages de données se chevauchent. Je pense questd::copy
permet le chevauchement des données, et donc il faut appelermemmove
.memcpy
. Cela me porte à croire que GCC vérifie s'il y a chevauchement de mémoire.std::copy
permet le chevauchement dans un sens mais pas dans l'autre. Le début de la sortie ne peut pas se trouver dans la plage d'entrée, mais le début de l'entrée peut se trouver dans la plage de sortie. C'est un peu étrange, car l'ordre des affectations est défini et un appel peut être UB même si l'effet de ces affectations, dans cet ordre, est défini. Mais je suppose que la restriction permet des optimisations de vectorisation.Toujours utiliser
std::copy
car ilmemcpy
est limité uniquement aux structures POD de style C, et le compilateur remplacera probablement les appels àstd::copy
parmemcpy
si les cibles sont en fait POD.De plus,
std::copy
peut être utilisé avec de nombreux types d'itérateurs, pas seulement avec des pointeurs.std::copy
est plus flexible pour aucune perte de performance et est clairement le gagnant.la source
std::copy(container.begin(), container.end(), destination);
copiera le contenu decontainer
(tout entrebegin
etend
) dans le tampon indiqué pardestination
.std::copy
ne nécessite pas de manigances comme&*container.begin()
ou&container.back() + 1
.En théorie,
memcpy
pourrait avoir une légère , imperceptible , infinitésimale , l' avantage de la performance, seulement parce qu'il n'a pas les mêmes exigences questd::copy
. Depuis la page de manuel dememcpy
:En d'autres termes,
memcpy
peut ignorer la possibilité de chevauchement des données. (Passer des tableaux quimemcpy
se chevauchent à est un comportement indéfini.) Ilmemcpy
n'est donc pas nécessaire de vérifier explicitement cette condition, alors qu'ilstd::copy
peut être utilisé tant que leOutputIterator
paramètre n'est pas dans la plage source. Notez que ce n'est pas la même chose que de dire que la plage source et la plage de destination ne peuvent pas se chevaucher.Donc, comme
std::copy
les exigences sont quelque peu différentes, en théorie, il devrait être légèrement (avec un accent extrême sur légèrement ) plus lent, car il vérifiera probablement le chevauchement des tableaux C, ou bien déléguera la copie des tableaux C àmemmove
, qui doit effectuer le vérifier. Mais en pratique, vous (et la plupart des profileurs) ne détecterez probablement même aucune différence.Bien sûr, si vous ne travaillez pas avec des POD , vous ne pouvez pas utiliser de
memcpy
toute façon.la source
std::copy<char>
. Maisstd::copy<int>
peut supposer que ses entrées sont alignées sur int. Cela fera une bien plus grande différence, car cela affecte chaque élément. Le chevauchement est un contrôle unique.memcpy
que j'ai vues vérifient l'alignement et tentent de copier des mots plutôt que octet par octet.memcpy
interface, il perd les informations d'alignement. Par conséquent,memcpy
doit effectuer des vérifications d'alignement au moment de l'exécution pour gérer les débuts et les fins non alignés. Ces chèques peuvent être bon marché mais ils ne sont pas gratuits. Alors questd::copy
peut éviter ces contrôles et vectoriser. En outre, le compilateur peut prouver que les tableaux source et de destination ne se chevauchent pas et se vectorisent à nouveau sans que l'utilisateur n'ait à choisir entrememcpy
etmemmove
.Ma règle est simple. Si vous utilisez C ++, préférez les bibliothèques C ++ et non C :)
la source
std::end(c_arr)
au lieu dec_arr + i_hope_this_is_the_right_number_of elements
est plus sûr? et peut-être plus important encore, plus clair. Et ce serait le point que j'insiste dans ce cas précis:std::copy()
est plus idiomatique, plus maintenable si les types d'itérateurs changent plus tard, conduit à une syntaxe plus claire, etc.std::copy
est plus sûr car il copie correctement les données transmises au cas où elles ne seraient pas de type POD.memcpy
se fera un plaisir de copier unstd::string
objet dans une nouvelle représentation octet par octet.Juste un ajout mineur: la différence de vitesse entre
memcpy()
etstd::copy()
peut varier un peu selon que les optimisations sont activées ou désactivées. Avec g ++ 6.2.0 et sans optimisationsmemcpy()
gagne clairement:Lorsque les optimisations sont activées (
-O3
), tout se ressemble à nouveau:Plus le tableau est grand, moins l'effet est perceptible, mais même à
N=1000
memcpy()
est environ deux fois plus rapide lorsque les optimisations ne sont pas activées.Code source (nécessite Google Benchmark):
la source
Si vous avez vraiment besoin de performances de copie maximales (ce que vous pourriez ne pas avoir), n'utilisez aucun des deux .
Il y a beaucoup à faire pour optimiser la copie de mémoire - encore plus si vous êtes prêt à utiliser plusieurs threads / cœurs pour cela. Voir, par exemple:
Qu'est-ce qui manque / sous-optimal dans cette implémentation memcpy?
la question et certaines des réponses ont suggéré des implémentations ou des liens vers des implémentations.
la source
Le profilage montre cette affirmation:
std::copy()
est toujours aussi rapidememcpy()
ou plus rapide est faux.Mon système:
Le code (langage: c ++):
Red Alert a souligné que le code utilise memcpy de tableau en tableau et std :: copy de tableau en vecteur. Cela pourrait être une raison pour un memcpy plus rapide.
Puisqu'il y a
v.reserve (sizeof (arr1));
il ne doit y avoir aucune différence dans la copie vers le vecteur ou le tableau.
Le code est fixe pour utiliser un tableau dans les deux cas. memcpy encore plus vite:
la source
std::copy
d'un vecteur à un tableau amemcpy
pris presque deux fois plus de temps? Ces données sont hautement suspectes. J'ai compilé votre code en utilisant gcc avec -O3, et l'assembly généré est le même pour les deux boucles. Ainsi, toute différence de temps que vous observez sur votre machine n'est que fortuite.