Question

Existe-t-il des alternatives plus rapides à memcpy () en C ++?

Était-ce utile?

La solution

Peu probable. Votre compilateur / bibliothèque standard aura probablement une implémentation très efficace et personnalisée de memcpy. Et memcpy est fondamentalement l’API la plus basse pour copier une partie de la mémoire dans une autre.

Si vous souhaitez des accélérations supplémentaires, trouvez un moyen de ne pas avoir besoin de copier de la mémoire.

Autres conseils

Tout d’abord, un conseil. Supposons que les personnes qui ont écrit votre bibliothèque standard ne soient pas stupides. S'il y avait un moyen plus rapide d'implémenter une mémoire générale, ils l'auraient fait.

Deuxièmement, oui, il existe de meilleures alternatives.

  • En C ++, utilisez la fonction std :: copy . Il fait la même chose, mais il est 1) plus sûr et 2) potentiellement plus rapide dans certains cas. C'est un modèle, ce qui signifie qu'il peut être spécialisé pour des types spécifiques, ce qui le rend potentiellement plus rapide que la mémoire C générale.
  • Ou, vous pouvez utiliser vos connaissances supérieures de votre situation . Les responsables de la mise en oeuvre de memcpy ont dû l'écrire pour que cela se passe bien dans tous les cas . Si vous avez des informations spécifiques sur la situation où vous en avez besoin, vous pourrez peut-être écrire une version plus rapide. Par exemple, combien de mémoire devez-vous copier? Comment est-il aligné? Cela pourrait vous permettre d’écrire une mémoire plus efficace pour ce cas spécifique . Mais ce ne sera pas aussi bon dans la plupart des autres cas (si ça va marcher du tout)

Agner Fog, expert en optimisation, a publié des fonctions de mémoire optimisées: http://agner.org/optimize/#asmlib . C'est sous GPL cependant.

Il y a quelque temps, Agner a déclaré que ces fonctions devraient remplacer les fonctions intégrées de GCC, car elles sont beaucoup plus rapides. Je ne sais pas si cela a été fait depuis.

Cette réponse à une question très similaire (à propos de memset () ) s'applique également à ici.

Cela indique en gros que les compilateurs génèrent un code très optimal pour memcpy () / memset () - et un code différent en fonction de la nature des objets (taille, alignement , etc.).

Et rappelez-vous, seuls les POD memcpy () en C ++.

Pour rechercher ou écrire une routine de copie rapide en mémoire, nous devons comprendre le fonctionnement des processeurs.

Les processeurs depuis Intel Pentium Pro effectuent des "exécutions dans le désordre". Ils peuvent exécuter de nombreuses instructions en parallèle si elles n’ont pas de dépendances. Mais ce n'est le cas que lorsque les instructions ne fonctionnent qu'avec des registres. Si elles fonctionnent avec de la mémoire, des unités centrales supplémentaires sont utilisées, appelées & # 8220; unités de charge & # 8221; (pour lire les données de la mémoire) et & # 8220; stocker des unités & # 8221; (pour écrire des données en mémoire). La plupart des unités centrales ont deux unités de charge et une unité de stockage, c’est-à-dire qu’elles peuvent exécuter en parallèle deux instructions lues dans la mémoire et une instruction écrite dans la mémoire (là encore, si elles ne s’affectent pas). La taille de ces unités est généralement la même que la taille maximale du registre & # 8211; si la CPU a des registres XMM (SSE) & # 8211; ses 16 octets, s’il comporte des registres YMM (AVX) & # 8211; c'est 32 octets et ainsi de suite. Toutes les instructions qui lisent ou écrivent de la mémoire sont converties en micro-opérations (micro-ops) qui vont au pool commun de micro-ops et attendent là que les unités de chargement et de stockage puissent les servir. Une seule unité de chargement ou de stockage ne peut servir qu’une seule micro-opération à la fois, quelle que soit la taille des données nécessaires au chargement ou au stockage, qu’il s’agisse d’un octet ou de 32 octets.

Ainsi, la copie de mémoire la plus rapide consisterait à se déplacer vers et à partir de registres de taille maximale. Pour les processeurs compatibles AVX, le moyen le plus rapide de copier de la mémoire consiste à répéter la séquence suivante, en boucle-déroulée:

vmovdqa     ymm0,ymmword ptr [rcx]
vmovdqa     ymm1,ymmword ptr [rcx+20h]
vmovdqa     ymmword ptr [rdx],ymm0
vmovdqa     ymmword ptr [rdx+20h],ymm1

Le code Google publié précédemment par hplbsh n’est pas très bon, car ils utilisent tous les registres 8 mm pour stocker les données avant de commencer à les réécrire, bien que cela ne soit pas nécessaire & # 8211; puisque nous avons seulement deux unités de charge et une unité de magasin. Donc, deux registres seulement donnent les meilleurs résultats. Utiliser autant de registres n'améliore en rien les performances.

Une routine de copie en mémoire peut également utiliser des fonctions "avancées". des techniques telles que & # 8220; prefetch & # 8221; demander au processeur de charger à l'avance la mémoire dans la mémoire cache et les & # 8220; écritures non temporelles & # 8221; (Si vous copiez de très gros morceaux de mémoire et que vous n’avez pas besoin de lire immédiatement les données du tampon de sortie), alignez-les par rapport aux écritures non alignées, etc.

Les processeurs modernes, commercialisés depuis 2013, s’ils ont le bit ERMS dans le CPUID, ont ce qu’on appelle le représentant amélioré movsb & # 8221 ;, donc pour les copies de mémoire volumineuses, le représentant movsb & # 8221 ; peut être utilisé & # 8211; la copie sera très rapide, voire plus rapide qu'avec les registres ymm, et fonctionnera correctement avec le cache. Toutefois, les coûts de démarrage de cette instruction sont très élevés & # 8211; environ 35 cycles, donc cela ne rapporte que sur des blocs de mémoire volumineux.

J'espère qu'il vous sera désormais plus facile de choisir ou d'écrire la meilleure routine de copie de mémoire nécessaire à votre cas.

Vous pouvez même conserver le memcpy / memmove standard, mais obtenez votre propre longmemcpy () spécial pour vos besoins.

En fonction de ce que vous essayez de faire ... si la mémoire est assez grande et que vous n'écrivez que faiblement sur la copie, une MMAP avec MMAP_PRIVATE pour créer un mappage de copie sur écriture pourrait être plus rapide .

En fonction de votre plate-forme, il peut y avoir des cas d'utilisation spécifiques, par exemple, si vous savez que la source et la destination sont alignées sur une ligne de cache et que la taille correspond à un multiple entier de la taille de la ligne de cache. En général, la plupart des compilateurs produiront un code assez optimal pour la mémoire.

Je ne suis pas sûr que l’utilisation de la mémoire par défaut soit toujours la meilleure option. La plupart des implémentations de mémoire que j'ai examinées ont tendance à essayer d'aligner les données au début, puis de faire des copies alignées. Si les données sont déjà alignées ou sont assez petites, cela fait perdre du temps.

Parfois, il est avantageux d'avoir une copie de mot spécialisée, une copie de mot demi, une copie d'octet memcpy, tant que cela n'a pas un effet trop négatif sur les caches.

De même, vous souhaiterez peut-être un contrôle plus précis de l’algorithme d’allocation actuel. Dans l'industrie du jeu vidéo, il est extrêmement courant que les gens écrivent leurs propres routines d'allocation de mémoire, quel que soit l'effort consenti par les développeurs de la chaîne d'outils pour la développer. Les jeux que j'ai vus ont presque toujours tendance à utiliser le Malloc de Doug Lea .

En règle générale, vous perdriez du temps à essayer d’optimiser la mémoire car il y aurait sans doute beaucoup de morceaux de code plus faciles dans votre application pour accélérer.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top