2009-11-11 5 views
29

Je réalise un traitement d'image en C qui nécessite de copier de gros morceaux de données autour de la mémoire - la source et la destination ne se chevauchent jamais.Memcpy très rapide pour le traitement d'image?

Quel est le moyen le plus rapide absolu de faire cela sur la plate-forme x86 en utilisant GCC (où SSE, SSE2 mais PAS SSE3 sont disponibles)?

Je prévois que la solution sera en assemblage ou en utilisant les intrinsèques GCC?

J'ai trouvé le lien suivant, mais ont aucune idée que ce soit la meilleure façon d'aller à ce sujet (l'auteur dit aussi qu'il a quelques bugs): http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html

EDIT: noter qu'une copie est nécessaire, je ne peux pas se déplacer avoir à copier les données (je pourrais expliquer pourquoi, mais je vais vous épargner l'explication :))

+0

pouvez-vous écrire votre code de sorte que la copie n'est pas nécessaire en premier lieu? – Ron

+0

Ron, non, je ne peux pas :( – horseyguy

+1

Si vous pouvez obtenir une prise sur le compilateur Intel, vous pourriez avoir de meilleures chances de l'optimiseur de convertir en instructions cpu vecteur –

Répondre

38

Courtesy de William Chan et Google. 30-70% plus rapide que memcpy dans Microsoft Visual Studio 2005.

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size) 
{ 

    __asm 
    { 
    mov esi, src; //src pointer 
    mov edi, dest; //dest pointer 

    mov ebx, size; //ebx is our counter 
    shr ebx, 7;  //divide by 128 (8 * 128bit registers) 


    loop_copy: 
     prefetchnta 128[ESI]; //SSE2 prefetch 
     prefetchnta 160[ESI]; 
     prefetchnta 192[ESI]; 
     prefetchnta 224[ESI]; 

     movdqa xmm0, 0[ESI]; //move data from src to registers 
     movdqa xmm1, 16[ESI]; 
     movdqa xmm2, 32[ESI]; 
     movdqa xmm3, 48[ESI]; 
     movdqa xmm4, 64[ESI]; 
     movdqa xmm5, 80[ESI]; 
     movdqa xmm6, 96[ESI]; 
     movdqa xmm7, 112[ESI]; 

     movntdq 0[EDI], xmm0; //move data from registers to dest 
     movntdq 16[EDI], xmm1; 
     movntdq 32[EDI], xmm2; 
     movntdq 48[EDI], xmm3; 
     movntdq 64[EDI], xmm4; 
     movntdq 80[EDI], xmm5; 
     movntdq 96[EDI], xmm6; 
     movntdq 112[EDI], xmm7; 

     add esi, 128; 
     add edi, 128; 
     dec ebx; 

     jnz loop_copy; //loop please 
    loop_copy_end: 
    } 
} 

Vous pouvez être en mesure d'optimiser davantage en fonction de votre situation exacte et les hypothèses que vous êtes en mesure de faire.

Vous pouvez également consulter la source memcpy (memcpy.asm) et supprimer son traitement spécial. Il peut être possible d'optimiser davantage!

+6

Remarque: les performances de cette memcopy dépendent fortement de la quantité de données à copier et de la taille du cache. Par exemple, les prélectures et les mouvements non temporels peuvent ralentir la performance pour des copies plus petites (s'ajustant dans L2) par rapport aux movdqa classiques. –

+2

Rampe: n'oublie pas de lui envoyer le code que tu as utilisé dans ton projet;) [http://williamchan.ca/portfolio/assembly/ssememcpy/source/viewsource.php?id=readme.txt] – ardsrk

+3

Je me souviens lire ce code dans un manuel AMD64 en premier. Et le code n'est pas optimal sur intel, où il a des problèmes d'alias de banque de cache. – hirschhornsalz

2

Si vous êtes sous Windows, utilisez les DirectX API, qui a GPU spécifiques routines -optimized pour une manipulation graphique (à quelle vitesse pourrait-il être? Votre CPU n'est pas chargé.) Faites autre chose pendant que le GPU le prend en charge.

Si vous voulez être indépendant du système d'exploitation, essayez OpenGL. Ne manipulez pas l'assembleur, car il est fort probable que vous échouerez lamentablement à surpasser les ingénieurs en construction de bibliothèques qui ont plus de 10 ans d'expérience.

+1

J'ai besoin qu'il soit exécuté en MEMORY, c'est-à-dire qu'il ne peut pas arriver sur le GPU. :) Aussi, je n'ai pas l'intention, moi-même, de surpasser les fonctions de la bibliothèque (d'où ma question ici), mais je suis sûr qu'il y a quelqu'un sur stackoverflow qui peut dépasser les libs. par les exigences de portabilité - comme je l'ai dit je me soucie uniquement de la plate-forme x86, donc peut-être d'autres optimisations spécifiques x86 sont possibles. – horseyguy

+0

+1 car c'est un bon premier conseil à donner - même s'il ne s'applique pas dans le cas de la rampe. – peterchen

+1

Je ne suis pas sûr que ce soit un bon conseil. Une machine moderne typique a à peu près la même bande passante mémoire pour le CPU et le GPU. Par exemple, les nombreux ordinateurs portables populaires utilisent des graphiques Intel HD, qui utilisent la même RAM que le processeur. La CPU peut déjà saturer le bus mémoire. Pour memcpy, je m'attendrais à des performances similaires sur le CPU ou le GPU. –

3

Si spécifique aux processeurs Intel, vous pouvez bénéficier de IPP. Si vous savez qu'il fonctionnera avec un GPU Nvidia, vous pouvez peut-être utiliser CUDA - dans les deux cas, il peut être préférable de regarder plus loin que l'optimisation de memcpy() - ils fournissent des opportunités pour améliorer votre algorithme à un niveau supérieur. Ils dépendent tous deux d'un matériel spécifique.

6

à tout niveau d'optimisation -O1 ou au-dessus, GCC utilisera les définitions BUILTIN pour des fonctions telles que memcpy - avec le paramètre -march droit (-march=pentium4 pour l'ensemble des fonctionnalités que vous mentionnez) il devrait générer assez optimal code en ligne spécifique à l'architecture.

Je le soumettrais à un test de performance et verrais ce qui en ressort.

6

Le code SSE publié par hapalibashi est le chemin à suivre.

Si vous avez besoin de plus de performances et ne craignez pas la longue et sinueuse route de l'écriture d'un pilote de périphérique: Toutes les plates-formes importantes disposent aujourd'hui d'un contrôleur DMA capable d'effectuer un travail de copie plus rapidement. parallèle au code CPU pourrait faire.

Cela implique d'écrire un pilote. Aucun système d'exploitation important dont je suis conscient n'expose cette fonctionnalité à l'utilisateur en raison des risques de sécurité.Cependant, cela peut valoir la peine (si vous avez besoin de la performance), car aucun code sur terre pourrait surpasser un morceau de matériel qui est conçu pour faire un tel travail.

+1

Je viens de poster une réponse qui parle de la bande passante de la RAM. Si ce que je dis est vrai, alors je ne pense pas que le moteur DMA pourrait atteindre beaucoup plus que ce que le CPU peut atteindre. Ai-je manqué quelque chose? –

5

Cette question a quatre ans maintenant et je suis un peu surpris que personne n'ait encore mentionné la bande passante mémoire. CPU-Z signale que ma machine a une RAM PC3-10700. Que la RAM a une bande passante maximale (aka taux de transfert, débit etc) de 10700 Mo/s. Le processeur de ma machine est un processeur i5-2430M, avec une fréquence de crête turbo de 3 GHz. En théorie, avec un processeur infiniment rapide et ma RAM, memcpy pourrait aller à 5300 MBytes/sec, soit la moitié de 10700 parce que memcpy doit lire et écrire dans la RAM. (edit: Comme v.oddou l'a souligné, c'est une approximation simpliste). D'un autre côté, imaginez que nous disposions d'une RAM infiniment rapide et d'un processeur réaliste, que pourrions-nous faire? Utilisons mon processeur 3 GHz comme exemple. S'il pouvait faire une lecture 32 bits et une écriture 32 bits à chaque cycle, il pourrait alors transférer 3 * 4 = 12000 MBytes/s. Cela semble facilement à portée de main pour un processeur moderne. Déjà, nous pouvons voir que le code qui s'exécute sur le processeur n'est pas vraiment le goulot d'étranglement. C'est l'une des raisons pour lesquelles les machines modernes ont des caches de données.

Nous pouvons mesurer ce que le processeur peut réellement faire en comparant le protocole memcpy lorsque nous savons que les données sont mises en cache. Faire cela avec précision est fastidieux. J'ai fait une application simple qui a écrit des nombres aléatoires dans un tableau, les memcpy'd à un autre tableau, puis vérifié les données copiées. J'ai traversé le code dans le débogueur pour m'assurer que le compilateur intelligent n'avait pas enlevé la copie. La modification de la taille de la matrice modifie les performances du cache: les petits tableaux s'adaptent dans le cache, les plus gros le sont moins. J'ai obtenu les résultats suivants:

  • 40 KByte tableaux: 16000 Mo/s
  • 400 KByte tableaux: 11000 Mo/s
  • 4000 tableaux de KByte: 3100 Mo/s

De toute évidence, mon CPU peut lire et écrire plus de 32 bits par cycle, puisque 16000 est plus que les 12000 que j'ai calculés théoriquement ci-dessus. Cela signifie que le processeur est encore moins un goulot d'étranglement que je pensais déjà. J'ai utilisé Visual Studio 2005, et en entrant dans l'implémentation standard de memcpy, je peux voir qu'il utilise l'instruction movqda sur ma machine. Je suppose que cela peut lire et écrire 64 bits par cycle. Le bon code hapalibashi affiché atteint 4200 Mo/sec sur ma machine - environ 40% plus rapide que l'implémentation VS 2005. Je suppose que c'est plus rapide car il utilise l'instruction prefetch pour améliorer les performances du cache. En résumé, le code qui s'exécute sur la CPU n'est pas le goulot d'étranglement et l'ajustement de ce code n'apportera que de petites améliorations.

+0

Votre processus de réflexion est bon. Cependant vous n'avez pas à penser à des numéros de marketing de la RAM, ce sont tous les chiffres pompés quad, ce qui ne correspond pas à la vitesse de 1 canal. Et c'est aussi la vitesse avant le bus, il y a aussi les frais généraux de gestion dans le modèle numa que les Core i7/opterons ont. –

Questions connexes