Je vais aller à l'encontre de la sagesse générale ici qui std::copyentraî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 était std::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 memcpyversion et la std::copyversion. Mon code tire parti de la saisie de données en aussi gros morceaux que possible (de nombreuses autres implémentations fonctionnent avec char/ char *, alors que j'opère avec T/ T *(où Test 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
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%
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
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%
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::copyn'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::copyaccé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
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%
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::copyaugmenter 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::copypeut (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, quand memcpyc'est légal, std::copyne devrait pas être pire. L'implémentation triviale de std::copyce report memcpydevrait 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::copyconserve également plus de ses informations. Lorsque vous appelez std::copy, la fonction conserve les types intacts. memcpyfonctionne void *, ce qui supprime presque toutes les informations utiles. Par exemple, si je passe dans un tableau de std::uint64_t, le compilateur ou l'implémenteur de la bibliothèque peut être en mesure de profiter de l'alignement 64 bits avec std::copy, mais il peut être plus difficile de le faire avec memcpy. 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::copyest dans une position intéressante. Je m'attends à ce qu'il ne soit jamais plus lent memcpyet parfois plus rapide avec n'importe quel compilateur d'optimisation moderne. De plus, tout ce que vous pouvez memcpy, vous le pouvez std::copy. memcpyne permet aucun chevauchement dans les tampons, alors que les std::copysupports se chevauchent dans un sens (avec std::copy_backwardpour l'autre sens de recouvrement). memcpyne fonctionne que sur des pointeurs, std::copyfonctionne 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 que std::copylorsque vous avez besoin de copier des morceaux de données.
charpeut être signé ou non signé, selon l'implémentation. Si le nombre d'octets peut être> = 128, utilisezunsigned charpour vos tableaux d'octets. (Le(int *)casting serait aussi plus sûr(unsigned int *).)