7
votes

Faire des caractères de copie du compilateur à l'aide de Movsd

Je voudrais copier une séquence de mémoire relativement courte (moins de 1 kb, typiquement 2-200 octets) dans une fonction de temps critique. Le meilleur code pour cela sur le côté CPU semble être REP Movsd code>. Cependant, je ne peux pas faire mon compilateur pour générer ce code. J'espérais (et je me souviens vaguement que je me souvienne de voir) à l'aide de Memcpy ferait ceci à l'aide de l'intrinsique intégré au compilateur, mais basée sur le démontage et le débogage, il semble que Compiler utilise la mise en œuvre de l'appel à MemCy / Memmove Bibliothèque. J'ai aussi espéré que le compilateur pourrait être suffisamment intelligent pour reconnaître la boucle suivante et utiliser Rep Movsd Code> par lui-même, mais il semble que ce ne soit pas.

char *dst;
const char *src;
// ...
for (int r=size; --r>=0; ) *dst++ = *src++;


0 commentaires

6 Réponses :


4
votes

Exécutez-vous une construction optimisée? Il n'utilisera pas un intrinsèque à moins d'optimisation. Il convient également de noter qu'il utilisera probablement une meilleure boucle de copie que REP Movsd. Il devrait essayer d'utiliser MMX, au moins, pour effectuer un 64 bits à une copie temporelle. En fait, 6 ou 7 ans de retour, j'ai écrit une boucle de copie optimisée MMX pour faire ce genre de chose. Malheureusement, le memcpy intrinsèque du compilateur a surperformé ma copie MMX d'environ 1%. Cela m'a vraiment appris de ne pas faire des hypothèses sur ce que le compilateur fait.


4 commentaires

Ce que je vois, c'est que le compilateur appelle la fonction Memmove générique. Cette fonction a été formidable dans la mise en place (en utilisant une copie alignée, MMX et même la SSE selon les besoins), mais ses frais de configuration sont trop élevés, et rend la fonction inappropriée pour la copie de quelques octets.


Ce n'est que si vous ne copiez que très peu d'octets, le Compielr optimisera même complètement le memcpy. Par exemple, si vous memcpy les 4 octets d'un flotteur dans un int (évitant ainsi tout aliasing potentiel), alors GCC et le compilateur MSVC supprimeront le MEMCY Complètement (j'ai vérifié cela). Il doit y avoir quelque chose que vous faites qui empêche la suppression du memcpy. Aussi.


... vous êtes correct, mais le problème est-il optimisé est absent uniquement lorsque le compilateur sait que c'est peu d'octets seulement (c'est-à-dire la taille du petit temps de compilation constante connu). Quand il ne le fait pas (la taille n'est pas compilée du temps connu), il suppose le cas le plus probable de la mise en œuvre de la bibliothèque de blocs et appelle la bibliothèque.


Hmm qui a du sens. Comment "pourrait" le compilateur d'optimiser cela. Comme je l'ai déjà dit, écrivez un assembleur et comparez des vitesses. Soit cela ou utilisez un commutateur qui fournit les implémentations les plus faciles de MEMCY afin qu'elles puissent être optimisées. Le commutateur ne sera vraiment pas un cache d'instructions sympathique, tant que vous seriez mieux que vous feriez mieux d'appeler MemCy.



-1
votes

Notez que pour utiliser MOVSD , src doit pointer sur une mémoire alignée sur une limite 32 bits et sa longueur doit être un multiple de 4 octets.

Si c'est le cas, pourquoi votre code utilise-t-il char * au lieu de int * ou quelque chose? Si ce n'est pas le cas, votre question est discutée.

Si vous changez char * sur int * , vous pourrait obtenir un meilleur résultat de std :: copie .

Edit: Avez-vous mesuré que la copie est le goulot d'étranglement?


3 commentaires

Movsb ferait aussi. Remarque: pendant que vous êtes correct sur la taille, Movsd ne nécessite pas d'alignement de DWORD de la cible ou de la source.


Il ne nécessite pas l'alignement, mais faisant non aligné Movsd ne sera pas très rapide.


Si vous êtes du tout préoccupé par la performance, alors oui, cela nécessite des données alignées. ;)



0
votes

Avez-vous chronométré memcpy? Sur les versions récentes de Visual Studio, la mise en œuvre MEMCY utilise SSE2 ... qui devrait être plus rapide que REP Movsd . Si le bloc que vous copiez est 1 Ko, ce n'est pas vraiment un problème que le compilateur n'utilise pas un intrinsèque depuis le temps de l'appel de la fonction sera négligeable par rapport au temps de la copie.


2 commentaires

Le bloc est inférieur à 1 kb. Parfois, quelques octets seulement, parfois 10, parfois ~ 200 B.


Ah ok. Que diriez-vous de décider au moment de l'exécution, en fonction de la taille du bloc à copier, d'appeler memcpy? Dites Taille> 32 (ou une autre valeur déterminée à être optimale), appelez MEMCY, sinon faites votre propre copie (optimisée par l'assemblage possible). Vous pouvez envelopper cette logique dans une fonction inline myMemCPY () .



6
votes

Plusieurs questions viennent à l'esprit.

Premièrement, comment savez-vous que Movsd serait plus rapide? Avez-vous regardé sa latence / débit? L'architecture X86 est pleine d'anciennes instructions de Crofty qui ne doivent pas être utilisées car elles ne sont tout simplement pas très efficaces sur les processeurs modernes.

second, que se passe-t-il si vous utilisez std :: copie au lieu de memcpy? std :: Copie est potentiellement plus rapide, car il peut être spécialisé à la compilation pour le type de données spécifique.

et troisième, avez-vous activé des fonctions intrinsèques sous Propriétés du projet -> C / C ++ -> Optimisation?

Bien sûr, je suppose que d'autres optimisations sont activées également.


0 commentaires

-1
votes

utiliser memcpy. Ce problème a déjà été résolu.

FYI REP Movsd n'est pas toujours le meilleur, le représentant Movsb peut être plus rapide dans certaines circonstances et avec SSE et similaire le meilleur est MOVNTQ [EDI], XMM0. Vous pouvez même optimiser davantage la quantité de mémoire en utilisant la localité de page en déplaçant des données sur un tampon, puis la déplaçant à votre destination.


4 commentaires

Je n'impose pas pour une grande quantité de mémoire. J'épuise pour des séquences copies courtes et j'ai trouvé que Memcpy setup surhead de manière inacceptable. Même une simple boucle que dans ma question fonctionne mieux que dans ce scénario.


Ceci est MemCY dans le code source VS 2005: tandis que (comptez--) {* (char *) dST = * (char *) src; dst = (char *) dst + 1; src = (char *) src + 1; } Qui utilisez-vous? Quelles optimisations?


Je pense que le problème est une couche d'abstraction au-dessus de votre memcpy. Le problème n'est pas que memcpy pour les petits tampons est lent, c'est que vous faites beaucoup de memcpy pour de petits tampons en premier lieu. Obtenez-vous les performances que vous voulez avec une mémoire écrite à la main Movsb?


Vs 2005. Êtes-vous sûr de la source? Dans mon cas, je peux voir memcpy.ase appelé, avec la même source en mettant en œuvre Memcpy et Memmove.



3
votes

à l'aide de MEMCY avec une taille constante

Qu'est-ce que j'ai trouvé entre-temps:

compilateur utilisera intrinsèque lorsque la taille du bloc copié est la date de compilation connue. Lorsqu'il n'est pas, appelle la mise en œuvre de la bibliothèque. Lorsque la taille est connue, le code généré est très agréable, sélectionné en fonction de la taille. Il peut s'agir d'un seul MOV ou Movsd, ou Movsd suivi de MOVSB, selon les besoins.

Il semble que si je veux vraiment utiliser MovsB ou Movsd toujours, même avec une taille "dynamique" que j'aurai utiliser l'assemblage en ligne ou intrinsèque spécial (voir ci-dessous). Je sais que la taille est "assez courte", mais le compilateur ne le sait pas et je ne peux même pas lui communiquer - j'ai même essayé d'utiliser __assume (taille <16), mais ce n'est pas suffisant.

Code de démonstration, compile avec "-Ob1 (expansion pour en ligne uniquement): xxx

intrinsique spécialisé

J'ai trouvé récemment il existe une façon très simple comment Pour fabriquer des caractères de copie de compilateur Visual Studio à l'aide de Movsd - très naturel et simple: en utilisant intrinsique. Suite à l'intrigue peut être pratique:


2 commentaires

Ensuite, votre meilleur pari est d'écrire un assembleur simple. Ce ne sera pas difficile. N'oubliez pas de le profiler contre le MEMCY pour vous assurer que vous obtenez réellement une victoire, la performance sage.


Que diriez-vous d'utiliser des blocs de taille fixe dans vos allocations? Allouez toujours en blocs de 32 ou 64 octets et copiez tout le temps. Je parierais que les 30 autres octets d'une copie sont à peine perceptibles.