Amélioré REP MOVSB pour memcpy

je voudrais utiliser la REP MOVSB améliorée (ERMSB) pour obtenir une large bande passante pour un memcpy personnalisé .

ERMSB a été introduit avec la microarchitecture du Pont Ivy. Si vous ne savez pas ce qu'est ERMSB, reportez-vous à la section "Amélioration des opérations de REP MOVSB et STOSB (ERMSB)" dans le manuel D'optimisation Intel .

le seul moyen que je connaisse pour le faire directement est avec inline assembly. J'ai obtenu la fonction suivante de https://groups.google.com/forum/#!topic / gnu.gcc.aide / - Bmlm_EG_fE

static inline void *__movsb(void *d, const void *s, size_t n) {
  asm volatile ("rep movsb"
                : "=D" (d),
                  "=S" (s),
                  "=c" (n)
                : "0" (d),
                  "1" (s),
                  "2" (n)
                : "memory");
  return d;
}

quand je l'utilise cependant, la bande passante est beaucoup plus faible qu'avec memcpy . __movsb obtient 15 GB/s et memcpy obtient 26 GB / s avec mon système i7-6700HQ (Skylake), Ubuntu 16.10, DDR4@2400 MHz Double Canal 32 GB, GCC 6.2.

pourquoi la bande passante est-elle si basse avec REP MOVSB ? Que puis-je faire pour l'améliorer?

voici le code que j'ai utilisé pour le tester.

//gcc -O3 -march=native -fopenmp foo.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <omp.h>
#include <x86intrin.h>

static inline void *__movsb(void *d, const void *s, size_t n) {
  asm volatile ("rep movsb"
                : "=D" (d),
                  "=S" (s),
                  "=c" (n)
                : "0" (d),
                  "1" (s),
                  "2" (n)
                : "memory");
  return d;
}

int main(void) {
  int n = 1<<30;

  //char *a = malloc(n), *b = malloc(n);

  char *a = _mm_malloc(n,4096), *b = _mm_malloc(n,4096);
  memset(a,2,n), memset(b,1,n);

  __movsb(b,a,n);
  printf("%dn", memcmp(b,a,n));

  double dtime;

  dtime = -omp_get_wtime();
  for(int i=0; i<10; i++) __movsb(b,a,n);
  dtime += omp_get_wtime();
  printf("dtime %f, %.2f GB/sn", dtime, 2.0*10*1E-9*n/dtime);

  dtime = -omp_get_wtime();
  for(int i=0; i<10; i++) memcpy(b,a,n);
  dtime += omp_get_wtime();
  printf("dtime %f, %.2f GB/sn", dtime, 2.0*10*1E-9*n/dtime);  
}

la raison pour laquelle je m'intéresse à rep movsb est basée sur ces commentaires

noter que sur Ivybridge et Haswell, avec des tampons de grandes dimensions pour s'adapter dans MLC vous pouvez battre movntdqa en utilisant rep movsb; movntdqa engage une RFO dans LLC, rep movsb ne le fait pas... le déplacement des rep est beaucoup plus rapide que le déplacement des DQA lorsque streaming à la mémoire sur Ivybridge et Haswell (mais soyez conscient que pré-Ivybridge il est lent!)

Que manque - /sous-optimal en ce memcpy mise en œuvre?


Voici mes résultats sur le même système de tinymembnech .

 C copy backwards                                     :   7910.6 MB/s (1.4%)
 C copy backwards (32 byte blocks)                    :   7696.6 MB/s (0.9%)
 C copy backwards (64 byte blocks)                    :   7679.5 MB/s (0.7%)
 C copy                                               :   8811.0 MB/s (1.2%)
 C copy prefetched (32 bytes step)                    :   9328.4 MB/s (0.5%)
 C copy prefetched (64 bytes step)                    :   9355.1 MB/s (0.6%)
 C 2-pass copy                                        :   6474.3 MB/s (1.3%)
 C 2-pass copy prefetched (32 bytes step)             :   7072.9 MB/s (1.2%)
 C 2-pass copy prefetched (64 bytes step)             :   7065.2 MB/s (0.8%)
 C fill                                               :  14426.0 MB/s (1.5%)
 C fill (shuffle within 16 byte blocks)               :  14198.0 MB/s (1.1%)
 C fill (shuffle within 32 byte blocks)               :  14422.0 MB/s (1.7%)
 C fill (shuffle within 64 byte blocks)               :  14178.3 MB/s (1.0%)
 ---
 standard memcpy                                      :  12784.4 MB/s (1.9%)
 standard memset                                      :  30630.3 MB/s (1.1%)
 ---
 MOVSB copy                                           :   8712.0 MB/s (2.0%)
 MOVSD copy                                           :   8712.7 MB/s (1.9%)
 SSE2 copy                                            :   8952.2 MB/s (0.7%)
 SSE2 nontemporal copy                                :  12538.2 MB/s (0.8%)
 SSE2 copy prefetched (32 bytes step)                 :   9553.6 MB/s (0.8%)
 SSE2 copy prefetched (64 bytes step)                 :   9458.5 MB/s (0.5%)
 SSE2 nontemporal copy prefetched (32 bytes step)     :  13103.2 MB/s (0.7%)
 SSE2 nontemporal copy prefetched (64 bytes step)     :  13179.1 MB/s (0.9%)
 SSE2 2-pass copy                                     :   7250.6 MB/s (0.7%)
 SSE2 2-pass copy prefetched (32 bytes step)          :   7437.8 MB/s (0.6%)
 SSE2 2-pass copy prefetched (64 bytes step)          :   7498.2 MB/s (0.9%)
 SSE2 2-pass nontemporal copy                         :   3776.6 MB/s (1.4%)
 SSE2 fill                                            :  14701.3 MB/s (1.6%)
 SSE2 nontemporal fill                                :  34188.3 MB/s (0.8%)

notez que sur mon système SSE2 copy prefetched est aussi plus rapide que MOVSB copy .


dans mes tests originaux, Je n'ai pas désactivé turbo. J'ai désactivé turbo et testé à nouveau et cela ne semble pas faire une grande différence. Cependant, changer la gestion de la puissance fait une grande différence.

Quand je fais

sudo cpufreq-set -r -g performance

je vois parfois plus de 20 GB/s avec rep movsb .

avec

sudo cpufreq-set -r -g powersave

le meilleur que je vois est d'environ 17 GO/s. Mais memcpy ne semble pas être sensible à la gestion de l'alimentation.


j'ai vérifié la fréquence (en utilisant turbostat ) avec et sans SpeedStep activé , avec performance et avec powersave pour le ralenti, une charge de 1 cœur et une charge de 4 cœur. J'ai lancé MKL dense matrix multiplication D'Intel pour créer une charge et régler le nombre de threads en utilisant OMP_SET_NUM_THREADS . Voici un tableau des résultats (nombres en GHz).

              SpeedStep     idle      1 core    4 core
powersave     OFF           0.8       2.6       2.6
performance   OFF           2.6       2.6       2.6
powersave     ON            0.8       3.5       3.1
performance   ON            3.5       3.5       3.1

cela montre qu'avec powersave même avec SpeedStep désactivé le CPU encore des horloges jusqu'à la fréquence de ralenti de 0.8 GHz . C'est seulement avec performance sans SpeedStep que le CPU fonctionne à une fréquence constante.

j'ai utilisé E. g sudo cpufreq-set -r performance (parce que cpufreq-set donnait des résultats étranges) pour changer les réglages de puissance. Ça remet turbo en marche donc j'ai dû désactiver turbo après.

48
demandé sur Z boson 2017-04-11 13:22:09

6 réponses

c'est un sujet qui me tient à cœur et qui a fait l'objet d'enquêtes récentes.je vais donc l'examiner sous plusieurs angles: l'histoire, quelques notes techniques (surtout académiques), les résultats des tests sur ma boîte, et enfin une tentative de répondre à votre question actuelle de savoir quand et où rep movsb pourrait avoir du sens.

en partie, il s'agit d'un appel à partager les résultats - si vous pouvez exécuter Tinymembench et partager les résultats avec des détails de votre configuration CPU et RAM serait parfaite. Surtout si vous avez une configuration à 4 canaux, une boîte de Pont Ivy, une boîte de serveur, etc.

histoire et conseil officiel

l'histoire de la performance des instructions de copie de chaîne rapide a été un peu une affaire d'escalier-c.-à-d., des périodes de performance stagnante alternant avec de grandes mises à niveau qui les ont mis en ligne ou même plus rapide que des approches concurrentes. Par exemple, il y a un saut dans performance à Nehalem (ciblant principalement les frais généraux de démarrage) et à nouveau à Ivy Bridge (ciblant le plus le débit total pour les grosses copies). Vous pouvez trouver un aperçu vieux de dix ans sur les difficultés de mise en œuvre des instructions rep movs d'un ingénieur Intel dans ce fil de discussion .

par exemple, dans les guides précédant L'introduction du Pont Ivy, le typique conseil est de les éviter ou de les utiliser très soigneusement 1 .

le guide actuel (bien, juin 2016) contient une variété de conseils confus et quelque peu incohérents, tels que: 2 :

La variante spécifique de la mise en œuvre est choisie au moment de l'exécution basé sur la disposition des données, l'alignement et la valeur du compteur (ECX). Pour exemple: MOVSB/STOSB avec le préfixe REP doit être utilisé avec counter valeur inférieure ou égale à trois pour le meilleur performance.

alors pour des copies de 3 octets ou moins? Vous n'avez pas besoin d'un préfixe rep pour cela en premier lieu, car avec une latence de démarrage revendiquée de ~9 cycles vous êtes presque certainement mieux avec un DWORD simple ou QWORD mov avec un peu de bit-twiddling pour masquer les octets inutilisés (ou peut-être avec 2 octet explicite, mot mov s si vous savez que la taille est exactement trois).

disent-ils:

les instructions de déplacement/stockage de chaîne de caractères ont plusieurs granularités de données. Pour un déplacement efficace des données, des granularités de données plus grandes sont préférables. Cela signifie qu'une meilleure efficacité peut être obtenue en décomposant un contre-valeur arbitraire dans un nombre de mots doubles plus un octet simple se déplace avec une valeur de comptage inférieure ou égale à 3.

cela semble certainement faux sur le matériel actuel avec ERMSB où rep movsb est au moins aussi rapide ou plus rapide, que le movd ou movq variantes pour les grands exemplaires.

en général, cette section (3.7.5) du guide actuel contient un mélange de conseils raisonnables et désuets. Il s'agit d'un produit courant des manuels Intel, car ils sont mis à jour de manière incrémentale pour chaque architecture (et prétendent couvrir près de deux décennies d'architectures même dans le manuel actuel), et les anciennes sections ne sont souvent pas mises à jour pour remplacer ou donnez des conseils conditionnels qui ne s'appliquent pas à l'architecture actuelle.

elles couvrent ensuite explicitement ERMSB à la section 3.7.6.

Je ne vais pas passer en revue les conseils restants de façon exhaustive, mais je vais résumer les bonnes parties dans le" pourquoi l'utiliser " ci-dessous.

D'autres affirmations importantes du guide sont que sur Haswell, rep movsb a été amélioré pour utiliser des opérations de 256 bits en interne.

Considérations Techniques

ce n'est qu'un résumé des avantages et des inconvénients sous-jacents que présentent les instructions rep du point de vue de la mise en œuvre .

avantages pour rep movs

  1. Lorsqu'une instruction rep movs est émise, le CPU sait qu'un bloc entier d'une taille connue doit être transférer. Cela peut l'aider à optimiser l'opération d'une manière qu'il ne peut pas avec des instructions discrètes, par exemple:

    • éviter la requête RFO quand elle sait que toute la ligne de cache sera écrasée.
    • émission immédiate et exacte des demandes de préfetch. La préfetching du matériel Fait du bon travail pour détecter les modèles de type memcpy , mais il faut quand même quelques lectures pour entrer en action et "over-prefetch" beaucoup de lignes de cache au-delà de la fin de la région copiée. rep movsb connaît exactement la taille de la région et peut préférer exactement.
  2. apparemment, il n'y a aucune garantie de commander parmi les magasins dans 3 un seul rep movs qui peut aider à simplifier le trafic de cohérence et simplement d'autres aspects du mouvement de bloc, par rapport à simple mov instructions qui doivent obéir à la commande de mémoire assez stricte 4 .

  3. en principe, l'instruction rep movs pourrait profiter de diverses astuces architecturales qui ne sont pas exposées dans L'ISA. Par exemple, les architectures peuvent avoir des chemins de données internes plus larges que L'ISA expose 5 et rep movs pourrait utiliser en interne.

désavantages

  1. rep movsb doit mettre en œuvre une sémantique spécifique qui peut être plus forte que les exigences logicielles sous-jacentes. En particulier, memcpy interdit les régions qui se chevauchent, et donc peut ignorer cette possibilité, mais rep movsb leur permet et doit produire le résultat attendu. Sur les implémentations en cours affecte principalement au démarrage de frais généraux, mais probablement pas au débit de gros blocs. De même, rep movsb doit supporter les copies byte-granulaires même si vous l'utilisez réellement pour copier de grands blocs qui sont un multiple d'une grande puissance de 2.

  2. le logiciel peut contenir des informations sur l'alignement, la taille de la copie et l'alias éventuel qui ne peuvent pas être communiquées au matériel en utilisant rep movsb . Les compilateurs peuvent souvent déterminer l'alignement des blocs de mémoire 6 et peuvent donc éviter une grande partie du travail de démarrage que rep movs doit faire sur chaque invocation.

Résultats Des Essais

Voici les résultats d'essais pour de nombreuses méthodes de copie différentes de tinymembench sur mon i7-6700HQ à 2,6 GHz (dommage que j'ai le CPU identique donc nous n'obtenons pas un nouveau point de données...):

 C copy backwards                                     :   8284.8 MB/s (0.3%)
 C copy backwards (32 byte blocks)                    :   8273.9 MB/s (0.4%)
 C copy backwards (64 byte blocks)                    :   8321.9 MB/s (0.8%)
 C copy                                               :   8863.1 MB/s (0.3%)
 C copy prefetched (32 bytes step)                    :   8900.8 MB/s (0.3%)
 C copy prefetched (64 bytes step)                    :   8817.5 MB/s (0.5%)
 C 2-pass copy                                        :   6492.3 MB/s (0.3%)
 C 2-pass copy prefetched (32 bytes step)             :   6516.0 MB/s (2.4%)
 C 2-pass copy prefetched (64 bytes step)             :   6520.5 MB/s (1.2%)
 ---
 standard memcpy                                      :  12169.8 MB/s (3.4%)
 standard memset                                      :  23479.9 MB/s (4.2%)
 ---
 MOVSB copy                                           :  10197.7 MB/s (1.6%)
 MOVSD copy                                           :  10177.6 MB/s (1.6%)
 SSE2 copy                                            :   8973.3 MB/s (2.5%)
 SSE2 nontemporal copy                                :  12924.0 MB/s (1.7%)
 SSE2 copy prefetched (32 bytes step)                 :   9014.2 MB/s (2.7%)
 SSE2 copy prefetched (64 bytes step)                 :   8964.5 MB/s (2.3%)
 SSE2 nontemporal copy prefetched (32 bytes step)     :  11777.2 MB/s (5.6%)
 SSE2 nontemporal copy prefetched (64 bytes step)     :  11826.8 MB/s (3.2%)
 SSE2 2-pass copy                                     :   7529.5 MB/s (1.8%)
 SSE2 2-pass copy prefetched (32 bytes step)          :   7122.5 MB/s (1.0%)
 SSE2 2-pass copy prefetched (64 bytes step)          :   7214.9 MB/s (1.4%)
 SSE2 2-pass nontemporal copy                         :   4987.0 MB/s

Quelques plats à emporter clés:

  • les méthodes rep movs sont plus rapides que toutes les autres méthodes qui ne sont pas" non temporelles " 7 , et considérablement plus rapide que les approches" C " qui copient 8 octets à la fois.
  • les méthodes" non temporelles "sont plus rapides, jusqu'à environ 26% que les méthodes rep movs -mais c'est un delta beaucoup plus petit que celui que vous avez signalé (26 GO/s vs 15 Go/s = ~73%).
  • si vous n'utilisez pas de mémoire non temporelle, l'utilisation de copies de 8 octets à partir de C est à peu près aussi bonne que 128 bits de large charge SSE/mémoire. C'est parce qu'une bonne copie boucle générer suffisamment de pression mémoire pour saturer la bande passante (par exemple, 2,6 GHz * 1 mémoire/cycle * 8 octets = 26 GO/s pour les mémoires).
  • il n'y a pas d'algorithmes explicites de 256 bits dans tinymembench (sauf probablement le" standard " memcpy ) mais cela n'a probablement pas d'importance en raison de la note ci-dessus.
  • l'augmentation du débit des approches de stockage non temporel par rapport aux approches temporelles est d'environ 1,45 x, ce qui est très proche du 1,5 x auquel on s'attendrait si NT élimine 1 sur 3 transferts (c'est à dire, 1 lire, 1 écrire pour NT vs 2 lit 1 écrire). Les approches rep movs se trouvent au milieu.
  • la combinaison d'une latence de mémoire assez faible et d'une bande passante modeste à 2 canaux signifie que cette puce en particulier est capable de saturer sa bande passante de mémoire à partir d'un seul fil, ce qui change le comportement de façon spectaculaire.
  • rep movsd semble utiliser la même magie que rep movsb sur cette puce. C'est intéressant parce que ERMSB cible seulement explicitement movsb et des tests antérieurs sur des arches plus tôt avec ERMSB montrent movsb effectuer beaucoup plus rapide que movsd . C'est surtout académique puisque movsb est plus général que movsd de toute façon.

Haswell

en regardant les Haswell results aimablement fournis par iwillnotexist dans les commentaires, nous voyons les mêmes tendances générales (la plupart des résultats pertinents extraits):

 C copy                                               :   6777.8 MB/s (0.4%)
 standard memcpy                                      :  10487.3 MB/s (0.5%)
 MOVSB copy                                           :   9393.9 MB/s (0.2%)
 MOVSD copy                                           :   9155.0 MB/s (1.6%)
 SSE2 copy                                            :   6780.5 MB/s (0.4%)
 SSE2 nontemporal copy                                :  10688.2 MB/s (0.3%)

l'approche rep movsb est encore plus lente que l'approche non temporelle memcpy , mais seulement d'environ 14% ici (comparativement à ~26% dans le test de Skylake). L'avantage des techniques NT au-dessus de leurs cousins temporels est maintenant ~57%, même un peu plus que l'avantage théorique de la réduction de bande passante.

Quand devez-vous utiliser rep movs ?

Enfin un coup de poignard à votre question: quand ou pourquoi devriez-vous utiliser? Il s'inspire de ce qui précède et présente quelques idées nouvelles. Malheureusement, il n'y a pas de réponse simple: vous devrez compenser divers facteurs, y compris certains que vous ne pouvez probablement pas savoir exactement, tels que les développements futurs.

une note que l'alternative à rep movsb peut être la version optimisée libc memcpy (y compris les copies insérées par le compilateur), ou il peut être une version roulée à la main memcpy . Certains des avantages ci-dessous s'appliquent seulement en comparaison à l'une ou l'autre de ces alternatives (par exemple, "simplicité" aide contre une version laminée à la main, mais pas contre intégré memcpy ), mais certains s'appliquent aux deux.

Restrictions sur les instructions disponibles

dans certains environnements Il ya une restriction sur certaines instructions ou l'utilisation de certains registres. Par exemple, dans le noyau Linux, l'utilisation des registres SSE/AVX ou FP est généralement interdite. Donc la plupart des variantes optimisées memcpy ne peuvent pas être utilisées car elles s'appuient sur les registres SSE ou AVX, et une simple copie 64 bits mov est utilisée sur x86. Pour ces plates-formes, l'utilisation de rep movsb permet la plupart des performances d'un optimisé memcpy sans briser la restriction sur le code SIMD.

un exemple plus général pourrait être le code qui doit cibler plusieurs générations de matériel, et qui n'utilise pas de dispatching spécifique au matériel (par exemple, en utilisant cpuid ). Ici, vous pourriez être forcé d'utiliser seulement des ensembles d'instruction plus anciens, ce qui exclut tout AVX, etc. rep movsb pourrait être une bonne approche ici car il permet un accès" caché " à des charges plus larges et des magasins sans utiliser de nouvelles instructions. Si vous ciblez le matériel pré-ERMSB, vous devriez voir si la performance rep movsb est acceptable là, cependant...

Future Proofing

un bel aspect de rep movsb est qu'il peut, en la théorie tire profit de l'amélioration architecturale sur les architectures futures, sans changements de source, que les mouvements explicites ne peuvent pas. Par exemple, lorsque les chemins de données de 256 bits ont été introduits, rep movsb a pu en profiter (comme le prétend Intel) sans aucune modification du logiciel. Le logiciel utilisant des mouvements de 128 bits (ce qui était optimal avant Haswell) devrait être modifié et recompilé.

est donc à la fois un avantage de maintenance du logiciel (no besoin de changer la source) et un avantage pour les binaires existants (pas besoin de déployer de nouveaux binaires pour tirer profit de l'amélioration).

l'importance de ce point dépend de votre modèle de maintenance (p. ex., la fréquence à laquelle les nouveaux binaires sont déployés en pratique) et de la très difficile évaluation de la vitesse probable de ces instructions à l'avenir. Au moins Intel est une sorte de guidage des utilisations dans cette direction cependant, en s'engageant à au moins raisonnable la performance dans l'avenir ( 15.3.3.6 ):

REP et REP MOVSB STOSB continuera à exécuter raisonnablement bien sur futurs Transformateurs.

chevauchement avec des travaux ultérieurs

cet avantage ne se retrouvera pas dans un repère simple memcpy bien sûr, qui, par définition, n'a pas de travaux ultérieurs à chevaucher, de sorte que l'ampleur de l'avantage serait doivent être soigneusement mesurés dans un scénario réel. Pour tirer le maximum d'avantages, il faudrait peut-être réorganiser le code entourant le memcpy .

cet avantage est souligné par Intel dans leur manuel d'optimisation (section 11.16.3.4) et dans leurs mots:

lorsque le nombre est connu pour être au moins mille octets ou plus, en utilisant L'amélioration du REP MOVSB/STOSB peut fournir un autre avantage pour amortir le le coût de la non-consommatrices de code. L'heuristique peut être comprise en utilisant une valeur de Cnt = 4096 et memset() comme exemple:

• une implémentation SIMD 256 bits de memset () devra émettre/exécuter retirer 128 cas d'exploitation de magasins de 32 octets avec VMOVDQA, avant les séquences d'instructions Non consommatrices peuvent faire leur chemin retraite.

• une instance de REPSTOSB amélioré avec ECX = 4096 est décodé comme un long flux micro-op fourni par le matériel, mais se retire comme un instruction. Il existe de nombreuses opérations store_data qui doivent être complétées avant que le résultat de memset() peut être consommé. Parce que l'achèvement d'exploitation de données de magasin est découplé de la retraite de commande de programme, un une partie importante du flux de code non consommateur peut être traitée par l'émission / l'exécution et la retraite, essentiellement gratuitement si le la séquence sans consommation n'est pas en concurrence pour les ressources tampons du magasin.

So Intel dit qu'après tout quelques uops le code après rep movsb a émis, mais alors que beaucoup de magasins sont encore en vol et le rep movsb dans son ensemble n'a pas encore retiré, uops de suivre des instructions peuvent faire plus de progrès à travers la machine hors-d'ordre qu'ils pourraient si ce code est venu après une boucle de copie.

les uops d'une boucle de charge et de stockage explicite doivent tous être retirés séparément dans l'ordre de programmation. Cela doit arriver pour faire de la place dans le ROB pour avoir suivi uops.

il ne semble pas y avoir beaucoup d'informations détaillées sur la longueur des instructions de micro-codage comme rep movsb travailler, exactement. Nous ne savons pas exactement comment les branches de micro-code demandent un flux différent de uops du séquenceur de micro-code, ni comment les UOPs se retirent. Si les UOP individuels ne doivent pas prendre leur retraite séparément, peut-être que l'instruction entière ne prend qu'un seul créneau dans le braquage?

quand le front-end qui alimente la machine OoO voit une instruction rep movsb dans le cache uop, elle active le microcode Sequencer ROM (MS-ROM) pour envoyer le microcode uops dans la file d'attente qui alimente l'étape d'émission/de renommage. Il n'est probablement pas possible pour d'autres uops de se mêler à cela et d'émettre/exécuter 8 alors que rep movsb est encore en cours d'émission, mais des instructions ultérieures peuvent être récupérées/décodées et émises juste après le dernier rep movsb uop, alors que certaines copies n'ont pas encore été exécutées. Ceci n'est utile que si au moins une partie de votre code suivant ne dépend pas du résultat du memcpy (ce qui n'est pas inhabituel).

maintenant, la taille de cet avantage est limitée: au plus, vous pouvez exécuter n instructions (uops en fait) au-delà de la lente rep movsb instruction, à quel point vous décrocherez, où N est le ROB taille . Avec les tailles ROB actuelles de ~200 (192 sur Haswell, 224 sur Skylake), c'est un avantage maximum de ~200 cycles de travail libre pour le code suivant avec une IPC de 1. En 200 cycles, vous pouvez copier quelque chose autour de 800 octets à 10 Go/ s, donc pour des copies de cette taille, vous pouvez obtenir du travail gratuit proche du coût de la copie (d'une certaine manière rendant la copie gratuite).

comme les tailles de copie deviennent beaucoup plus grandes, cependant, l'importance relative de ceci diminue rapidement (par exemple, si vous copiez 80 KB à la place, le travail libre est seulement 1% du coût de copie). Il n'en reste pas moins intéressant pour les copies de taille modeste.

Les boucles de copie

ne bloquent pas totalement l'exécution des instructions ultérieures. Intel n'entre pas dans les détails sur l'importance de l'avantage, ou sur le type de copies ou de code environnant il ya le plus d'avantages. (Destination ou source chaude ou froide, ILP élevé ou faible code de haute latence ILP après).

Taille De Code

la taille de code exécutée (quelques octets) est microscopique comparée à une routine optimisée typique memcpy . Si la performance est du tout limitée par les erreurs de I-cache (y compris UOP cache), la taille de code réduite pourrait être bénéfique.

encore une fois, nous pouvons lier l'ampleur de cet avantage en fonction de la taille de la copie. Je ne vais pas vraiment le résoudre numériquement, mais l'intuition est que réduire la taille du code dynamique par les octets B peut sauver au plus C * B cache-manques, pour une certaine constante C. Chaque appel à memcpy entraîne le coût de la perte de cache (ou avantage) une fois, mais l'avantage des échelles de débit plus élevées avec le nombre d'octets copiés. Ainsi, pour les transferts importants, un débit plus élevé dominera les effets de cache.

encore une fois, ce n'est pas quelque chose qui apparaîtra dans un benchmark simple, où l'ensemble de la boucle s'insérera sans doute dans le cache uop. Vous aurez besoin d'un test en place pour évaluer cet effet.

Optimisation Spécifique À L'Architecture

vous avez déclaré que sur votre matériel, rep movsb était considérablement plus lent que la plate-forme memcpy . Cependant, même ici il y a des rapports du résultat opposé sur le matériel plus tôt (comme Ivy Bridge).

c'est tout à fait plausible, car il semble que les opérations de string move obtiennent l'amour périodiquement - mais pas toutes les générations, de sorte qu'il peut bien être plus rapide ou au moins lié (à ce moment-là, il peut gagner basé sur d'autres avantages) sur les architectures où il a été amené à date, seulement pour tomber en retard dans le matériel subséquent.

citant Andy Glew, qui devrait savoir une chose ou deux à ce sujet après avoir mis en œuvre ces Sur le P6:

la grande faiblesse de ce qui va vite chaînes dans microcode était [...] le microcode est tombé d'accord avec chaque génération, devient plus lent et plus lentement jusqu'à ce que quelqu'un arrive à la réparer. Tout comme une bibliothèque hommes copie des chutes de musique. Je suppose qu'il est possible que l'un des les occasions manquées étaient d'utiliser des chargements et des est devenu disponible, et ainsi de suite.

dans ce cas, il peut être considéré comme une autre optimisation" spécifique à la plate-forme "à appliquer dans les routines typiques memcpy que vous trouverez dans les bibliothèques standard et les compilateurs JIT: mais seulement pour une utilisation sur les architectures où il est mieux. Pour les compilations JIT ou AOT, c'est facile, mais pour les compilations statiques binaires compilés cela nécessite une expédition spécifique à la plate-forme, mais qui existe souvent déjà (parfois implémentée au moment de la liaison), ou l'argument mtune peut être utilisé pour prendre une décision statique.

simplicité

même sur Skylake, où il semble qu'il est tombé derrière les techniques non-temporelles les plus rapides absolues, il est encore plus rapide que la plupart des approches et est très simple . Cela signifie moins de temps en validation, moins de bogues mystères, moins de temps de mise au point et de mise à jour d'une implémentation monster memcpy (ou, à l'inverse, moins de dépendance aux caprices des implémentations de la bibliothèque standard si vous vous y fiez).

Plates-Formes Liées À La Latence

Mémoire de débit lié à des algorithmes de 9 peut être fait en deux principaux régimes: DRAM de la bande passante liée ou de simultanéité/temps de latence lié.

le le premier mode est celui que vous connaissez probablement: le sous-système DRAM possède une certaine largeur de bande théorique que vous pouvez calculer assez facilement en fonction du nombre de canaux, du débit/largeur de données et de la fréquence. Par exemple, mon système DDR4-2133 avec 2 canaux a une bande passante maximale de 2.133 * 8 * 2 = 34,1 GB / s, identique à rapporté sur ARK .

vous ne supporterez pas plus que ce taux de DRAM (et généralement un peu moins en raison de divers inefficiencies) ajouté à tous les cœurs sur la socket (i.e., il s'agit d'une limite globale pour les systèmes à une seule socket).

l'autre limite est imposée par le nombre de requêtes simultanées qu'un core peut effectivement émettre vers le sous-système mémoire. Imaginez qu'un noyau ne puisse avoir qu'une requête en cours d'exécution à la fois, pour une ligne de cache de 64 octets-une fois la requête terminée, vous pourriez en émettre une autre. Supposons aussi une latence de mémoire de 50ns très rapide. Alors malgré la large bande passante 34,1 GO/s DRAM, vous en fait, on n'obtient que 64 octets / 50 ns = 1,28 GB / s, soit moins de 4% de la bande passante maximale.

dans la pratique, les noyaux peuvent émettre plus d'une requête à la fois, mais pas un nombre illimité. Il est généralement entendu qu'il n'y a que 10 line fill buffers par noyau entre la L1 et le reste de la hiérarchie de mémoire, et peut-être 16 ou si remplir tampons entre L2 et DRAM. Préfetching concurrence pour les mêmes ressources, mais au moins contribue à réduire la efficace de latence. Pour plus de détails Regardez l'un des grands messages Dr.Bandwidth a écrit sur le thème , la plupart du temps sur les forums Intel.

Encore, plus les récents Processeurs sont limitées par ce facteur, pas la RAM de la bande passante. En général, ils atteignent 12 à 20 Go / s par cœur, alors que la bande passante de la mémoire vive peut être de 50+ Go/s (sur un système à 4 canaux). Seuls quelques noyaux de "clients" à 2 canaux de la gen récente, qui semblent pour avoir un meilleur uncore, peut-être plus de tampons de ligne peut frapper la limite DRAM sur un seul noyau, et nos puces Skylake semblent être l'un d'eux.

maintenant, bien sûr, il y a une raison pour laquelle Intel conçoit des systèmes avec une bande passante DRAM de 50 Go/s, tout en ne supportant que < 20 Go/s par core en raison de limites de simultanéité: la première limite est à l'échelle de la socket et la seconde est par core. Ainsi, chaque noyau sur un système 8 core peut pousser 20 Go / s de requêtes, à quel point elles seront limitées DRAM encore.

pourquoi je n'arrête pas de parler de ça? Parce que la meilleure mise en œuvre memcpy dépend souvent du régime dans lequel vous opérez. Une fois que vous êtes limité Dram BW (comme nos puces sont apparemment, mais la plupart ne sont pas sur un seul noyau), l'utilisation non-temporelle écrit devient très important car il sauve la lecture-pour-propriété qui normalement gaspille 1/3 de votre bande passante. Vous voyez cela exactement dans les résultats de test ci-dessus: les implémentations memcpy que ne utiliser les magasins NT perdent 1/3 de leur bande passante.

cependant, si vous êtes limité par la concurrence, la situation s'égalise et parfois s'inverse. Vous avez de la bande passante DRAM à épargner, donc les magasins NT n'aident pas et ils peuvent même faire mal puisqu'ils peuvent augmenter la latence puisque le temps de transfert pour le tampon de ligne peut être plus long qu'un scénario où prefetch apporte la ligne RFO dans LLC (ou même L2) et puis le magasin complète dans LLC pour un efficace inférieur le temps de latence. Enfin, server uncores ont tendance à avoir des magasins NT beaucoup plus lents que les magasins clients (et la bande passante élevée), ce qui accentue cet effet.

donc sur d'autres plateformes vous pourriez trouver que les magasins NT sont moins utiles (au moins quand vous vous souciez de la performance mono-threadée) et peut-être rep movsb Gagne où (si elle obtient le meilleur des deux mondes).

vraiment, ce dernier point est un appel pour la plupart des tests. Je sais que NT les magasins perdent leur avantage apparent pour les tests filetés simples sur la plupart des arcs (y compris les arcs de serveur actuels), mais je ne sais pas comment rep movsb va fonctionner relativement...

Références

autres bonnes sources d'information non intégrées dans ce qui précède.

comp.arch enquête de rep movsb par rapport à des solutions de rechange. Beaucoup de bonnes notes sur la prédiction de la branche, et une mise en œuvre de la approche j'ai souvent suggéré pour les petits blocs: en utilisant le chevauchement première et/ou dernière lecture / écrit plutôt que d'essayer d'écrire seulement exactement le nombre requis d'octets (par exemple, la mise en œuvre de toutes les copies de 9 à 16 octets comme deux copies de 8 octets qui pourraient se chevaucher dans un maximum de 7 octets).


1 L'intention est probablement de la limiter aux cas où, par exemple, la taille du code est très importante.

2 voir , Section 3.7.5: , préfixe REP et mouvement des données.

3 il est important de noter que cela ne s'applique qu'aux divers magasins visés par l'instruction unique elle-même: une fois terminée, le bloc de magasins apparaît toujours commandé par rapport aux magasins antérieurs et subséquents. Donc le code pouvez voir les magasins de la rep movs out of order à l'égard les uns des autres mais pas en ce qui concerne les stocks antérieurs ou ultérieurs (et c'est cette dernière garantie dont vous avez habituellement besoin). Il ne sera un problème si vous utilisez la fin de la destination de la copie comme un drapeau de synchronisation, au lieu d'un magasin séparé.

4 notez que les magasins discrets non temporels évitent également la plupart des exigences de commande, bien que dans la pratique rep movs ait encore plus de liberté puisqu'il y a encore quelques contraintes de commande sur les magasins WC/NT.

5 C'était courant dans la dernière partie de l'ère des 32 bits, où de nombreuses puces avaient des chemins de données 64 bits (E. g, to support FPUs which had support for the 64-bit double type). Aujourd'hui, les puces" castrées "telles que les marques Pentium ou Celeron ont désactivé AVX, mais probablement rep movs microcode peut encore utiliser 256b charges/magasins.

6 par exemple, en raison des règles d'alignement des langues, l'alignement attributs ou opérateurs, règles d'Alias ou autres informations déterminées au moment de la compilation. Dans le cas de l'alignement, même si l'alignement exact ne peut pas être déterminé, ils peuvent au moins être en mesure de hisser les contrôles d'alignement hors des boucles ou d'éliminer autrement les contrôles redondants.

7 je fais l'hypothèse que" standard " memcpy est le choix d'une approche non temporelle, ce qui est très probable pour cette taille de tampon.

8 ce n'est pas nécessairement évident, car il se pourrait que le flux uop généré par le rep movsb monopolise simplement l'expédition et alors il ressemblerait beaucoup au cas explicite mov . Il semble que cela ne fonctionne pas comme cela cependant-uops des instructions suivantes peuvent se mêler avec uops du micro-codé rep movsb .

9 c'est-à-dire: ceux qui peuvent émettre un nombre de demandes de mémoire indépendantes et donc saturer la bande passante DRAM-to-core disponible, dont memcpy serait un poster d'enfant (et comme appliqué à des charges purement liées à la latence telles que la chasse à l'aiguille).

50
répondu BeeOnRope 2017-09-26 22:21:41

Enhanced REP MOVSB (Ivy Bridge and later)

microarchitecture Ivy Bridge (processeurs libérés en 2012 et 2013) introduit Enhanced REP MOVSB (nous avons encore besoin de vérifier le bit correspondant) et nous a permis de copier la mémoire rapidement.

les versions les moins chères des processeurs postérieurs-Kaby Lake Celeron et Pentium, sorti en 2017, n'ont pas AVX qui aurait pu être utilisé pour la copie de mémoire rapide, mais ont encore le Amélioré REP MOVSB.

REP MOVSB (ERMSB) est seulement plus rapide que AVX copie ou utilisation générale enregistrer une copie si la taille de bloc est d'au moins 256 octets. Pour les blocs en dessous de 64 octets, c'est beaucoup plus lent, car il y a un démarrage interne élevé dans ERMSB - environ 35 cycles.

voir le manuel Intel sur L'optimisation, section 3.7.6." http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf

  • le coût de démarrage est de 35 cycles;
  • les adresses source et destination doivent être alignées sur une limite de 16 octets;
  • la région source ne doit pas chevaucher la région de destination;
  • la longueur doit être un multiple de 64 à de produire une meilleure performance;
  • la direction doit être vers l'avant (CLD).

comme je l'ai dit plus tôt, REP MOVSB commence à surpasser d'autres méthodes lorsque la longueur est d'au moins 256 octets, mais pour voir l'avantage clair sur la copie AVX, la longueur doit être plus de 2048 octets.

sur L'effet d'alignement si REP MOVSB vs. AVX copy, le manuel Intel donne les informations suivantes:

  • si le tampon source n'est pas aligné, l'impact sur la mise en œuvre ERMSB par rapport à l'AVX de 128 bits est similaire;
  • si le tampon de destination n'est pas aligné, l'impact sur la mise en œuvre de ERMSB peut être une dégradation de 25%, tandis que la mise en œuvre de memcpy AVX de 128 bits peut dégrader seulement 5%, par rapport au scénario aligné de 16 octets.

j'ai fait des tests sur Intel Core i5-6600, sous 64 bits, et j'ai comparé REP MOVSB memcpy () avec un simple MOV RAX, [SRC]; MOV [DST], RAX implementation quand les données correspondent à L1 cache :

REP MOVSB memcpy ():

 - 1622400000 data blocks of  32 bytes took 17.9337 seconds to copy;  2760.8205 MB/s
 - 1622400000 data blocks of  64 bytes took 17.8364 seconds to copy;  5551.7463 MB/s
 - 811200000 data blocks of  128 bytes took 10.8098 seconds to copy;  9160.5659 MB/s
 - 405600000 data blocks of  256 bytes took  5.8616 seconds to copy; 16893.5527 MB/s
 - 202800000 data blocks of  512 bytes took  3.9315 seconds to copy; 25187.2976 MB/s
 - 101400000 data blocks of 1024 bytes took  2.1648 seconds to copy; 45743.4214 MB/s
 - 50700000 data blocks of  2048 bytes took  1.5301 seconds to copy; 64717.0642 MB/s
 - 25350000 data blocks of  4096 bytes took  1.3346 seconds to copy; 74198.4030 MB/s
 - 12675000 data blocks of  8192 bytes took  1.1069 seconds to copy; 89456.2119 MB/s
 - 6337500 data blocks of  16384 bytes took  1.1120 seconds to copy; 89053.2094 MB/s

MOV RAX... memcpy ():

 - 1622400000 data blocks of  32 bytes took  7.3536 seconds to copy;  6733.0256 MB/s
 - 1622400000 data blocks of  64 bytes took 10.7727 seconds to copy;  9192.1090 MB/s
 - 811200000 data blocks of  128 bytes took  8.9408 seconds to copy; 11075.4480 MB/s
 - 405600000 data blocks of  256 bytes took  8.4956 seconds to copy; 11655.8805 MB/s
 - 202800000 data blocks of  512 bytes took  9.1032 seconds to copy; 10877.8248 MB/s
 - 101400000 data blocks of 1024 bytes took  8.2539 seconds to copy; 11997.1185 MB/s
 - 50700000 data blocks of  2048 bytes took  7.7909 seconds to copy; 12710.1252 MB/s
 - 25350000 data blocks of  4096 bytes took  7.5992 seconds to copy; 13030.7062 MB/s
 - 12675000 data blocks of  8192 bytes took  7.4679 seconds to copy; 13259.9384 MB/s

ainsi, même sur des blocs de 128 bits, REP MOVSB est plus lent qu'une simple copie MOV RAX en boucle (non déroulée). La mise en œuvre D'ERMSB commence à dépasser la boucle MOV RAX seulement en commençant des blocs de 256 octets.

Normal (pas améliorées) REP MOV sur Nehalem et plus tard

étonnamment, les architectures précédentes (Nehalem et plus tard), qui n'avaient pas encore amélioré REP MOVB, avaient une implémentation REP MOVSD/MOVSQ assez rapide (mais pas REP MOVSB/MOVSW) pour les gros blocs, mais pas assez grande pour dépasser le cache L1.

Intel Optimization Manual (2.5.6 REP String Enhancement) donne les informations suivantes est liée à Nehalem microarchitecture-processeurs Intel Core i5, i7 et Xeon sorti en 2009 et 2010.

REP MOVSB

la latence pour MOVSB, est de 9 cycles si ECX < 4; sinon REP MOVSB avec ECX > 9 ont un coût de démarrage de 50 cycles.

  • chaîne minuscule (ECX < 4): la latence de REP MOVSB est de 9 cycles;
  • petite chaîne (ECX est entre 4 et 9): aucune information officielle dans le manuel Intel, probablement plus de 9 cycles mais moins de 50 cycles;
  • chaîne longue (ECX > 9): coût de démarrage à 50 cycles.

ma conclusion: REP MOVSB est presque inutile sur Nehalem.

MOVSW / MOVSD/MOVSQ

citation du manuel D'optimisation Intel (2.5.6 Rep String Enhancement):

  • chaîne courte (ECX < = 12): la latence de REP MOVSW / MOVSD/MOVSQ est d'environ 20 cycle.
  • chaîne rapide (ECX > = 76: à L'exclusion de REP MOVSB): l'implémentation du processeur permet d'optimiser le matériel en déplaçant autant de morceaux de données que possible en 16 octets. La latence de la latence de la chaîne REP variera si l'un des transferts de données de 16 octets s'étend au-delà de la limite de la ligne de cache.: = Split-free: la latence se compose d'un coût de démarrage d'environ 40 cycles et chaque 64 octets de données ajoute 4 cycles. = Partage de Cache: la latence se compose d'un coût de démarrage d'environ 35 cycles et chaque 64 octets de données ajoute 6 cycles.
  • longueurs de chaîne intermédiaire: la latence de REP MOVSW/MOVSD/MOVSQ a un coût de démarrage d'environ 15 cycles plus un cycle pour chaque itération du mouvement de données dans word/dword / qword.

Intel ne semble pas être correct ici. De la citation ci-dessus nous comprenons que pour de très grands blocs de mémoire, REP MOVSW est aussi rapide que REP MOVSD/MOVSQ, mais des tests ont montré que seul REP MOVSD/MOVSQ sont rapides, tandis que REP MOVSW est encore plus lent que REP MOVSB sur Nehalem et Westmere.

selon les informations fournies par Intel dans le manuel, sur les précédentes microarchitectures Intel (avant 2008), les coûts de démarrage sont encore plus élevés.

Conclusion: Si vous avez juste besoin de copier des données qui s'adaptent au cache L1, seulement 4 cycles pour copier 64 octets de données est excellent, et vous n'avez pas besoin d'utiliser des registres XMM!

REP MOVSD / MOVSQ est la solution universelle qui fonctionne parfaitement sur tous les processeurs Intel (aucun ERMSB requis) si les données correspondent à la mémoire cache L1

Voici les tests de REP MOVS* lorsque la source et la destination se trouvaient dans le cache L1, de blocs assez grands pour ne pas être sérieusement affectés par les coûts de démarrage, mais pas trop grands pour dépasser la taille du cache L1. Source: http://users.atw.hu/instlatx64 /

Yonah (2006-2008)

    REP MOVSB 10.91 B/c
    REP MOVSW 10.85 B/c
    REP MOVSD 11.05 B/c

Nehalem (2009-2010)

    REP MOVSB 25.32 B/c
    REP MOVSW 19.72 B/c
    REP MOVSD 27.56 B/c
    REP MOVSQ 27.54 B/c

Westmere (2010-2011)

    REP MOVSB 21.14 B/c
    REP MOVSW 19.11 B/c
    REP MOVSD 24.27 B/c

Ivy Bridge (2012-2013)- with Enhanced REP MOVSB

    REP MOVSB 28.72 B/c
    REP MOVSW 19.40 B/c
    REP MOVSD 27.96 B/c
    REP MOVSQ 27.89 B/c

SkyLake (2015-2016) - avec REP MOVSB améliorée

    REP MOVSB 57.59 B/c
    REP MOVSW 58.20 B/c
    REP MOVSD 58.10 B/c
    REP MOVSQ 57.59 B/c

Kaby Lac (2016-2017) - avec un renforcement de REP MOVSB

    REP MOVSB 58.00 B/c
    REP MOVSW 57.69 B/c
    REP MOVSD 58.00 B/c
    REP MOVSQ 57.89 B/c

comme vous le voyez, la mise en œuvre des MOV REP diffère considérablement d'une microarchitecture à l'autre. Sur certains processeurs, comme Ivy Bridge - REP MOVSB est le plus rapide, bien que légèrement plus rapide que REP MOVSD/MOVSQ, mais aucun doute que sur tous les processeurs depuis Nehalem, REP MOVSD/MOVSQ fonctionne très bien - vous n'avez même pas besoin de "Rep MOVSB amélioré", puisque, sur Ivy Bridge (2013) avec ENHACNCED REP MOVSB , REPSD affiche le même octet par horloge que sur Nehalem (2010) sans Enhacnced REP movsb , alors QU'en fait REP Le MOVSB est devenu très rapide seulement depuis SkyLake (2015) - deux fois plus rapide que sur le Pont Ivy. Donc ce REP MOVSB bit dans le CPUID peut être déroutant - il montre seulement que REP MOVSB en soi est OK, mais pas que tout REP MOVS* est plus rapide.

L'implémentation la plus confuse de L'ERMBSB se trouve sur la microarchitecture du Pont Ivy. Oui, sur les processeurs très anciens, avant ERMSB, REP MOVS* pour les gros blocs utilisait une fonctionnalité de protocole de cache qui n'est pas disponible au code régulier (no-RFO). Mais ce protocole n'est plus utilisé sur Ivy Bridge qui a ERMSB. Selon les commentaires D'Andy Glew sur une réponse à" pourquoi memcpy/memset compliqué supérieur?"d'une réponse de Peter Cordes , une fonctionnalité de protocole de cache qui n'est pas disponible pour le code régulier était autrefois utilisée sur les processeurs plus anciens, mais plus sur Ivy Bridge. Et il vient une explication de la raison pour laquelle les coûts de démarrage sont si élevés pour REP MOVS*: "Les frais généraux importants pour choisir et la mise en place de la bonne méthode est principalement due à l'absence de prédiction de branche de microcode". Il est également intéressant de noter que Pentium Pro (P6) a mis en œuvre en 1996 des REP MOVS* avec 64 bits de charge et de stockage de micro-code et un protocole de cache no - RFO-ils ne violent pas l'ordre de mémoire, contrairement à ERMSB dans Ivy Bridge.

Avertissement

  1. cette réponse n'est pertinente que dans les cas où la source et la destination des données correspondent au cache L1. Selon les circonstances, les particularités de l'accès à la mémoire (cache, etc. doit être pris en considération. Le Prefetch et le NTI peuvent donner de meilleurs résultats dans certains cas, en particulier sur les processeurs qui Je n'avais pas encore le MOVSB REP amélioré. Même sur ces processeurs plus anciens, REP MOVSD a pu utiliser une fonctionnalité de protocole de cache qui n'est pas disponible pour le code régulier.
  2. les informations contenues dans cette réponse ne concernent que les processeurs Intel et non les processeurs par d'autres fabricants comme AMD qui peuvent avoir des implémentations meilleures ou pires des instructions movs* de REP.
  3. j'ai présenté les résultats d'essais pour SkyLake et Kaby Lake juste pour la confirmation - ces architectures ont les mêmes données de cycle par instruction.
  4. tous les noms de produits, marques et marques déposées sont la propriété de leurs propriétaires respectifs.
8
répondu Maxim Masiutin 2017-09-06 23:58:34

Vous dites que vous voulez:

une réponse qui montre quand ERMSB est utile

mais je ne suis pas sûr que ça signifie ce que vous pensez que ça signifie. En regardant le 3.7.6.1 docs que vous liez, il dit explicitement:

implémenter memcpy en utilisant ERMSB pourrait ne pas atteindre le même niveau de débit que l'utilisation des alternatives AVX 256-bits ou 128-bits, selon la longueur et les facteurs d'alignement.

donc juste parce que CPUID indique le support pour ERMSB, ce n'est pas une garantie que REP MOVSB sera le moyen le plus rapide pour copier de la mémoire. Ça veut juste dire que ça ne sera pas aussi nul que dans d'autres CPU.

cependant, ce n'est pas parce qu'il y a des alternatives qui peuvent, dans certaines conditions, courir plus vite que REP MOVSB qui est inutile. Maintenant que les pénalités que cette instruction d'engager ont disparu, il est potentiellement un encore des instructions utiles.

rappelez-vous, c'est un tout petit peu de code (2 octets!) par rapport à certains des plus impliqués memcpy routines que j'ai vu. Comme le chargement et l'exécution de gros morceaux de code comportent également une pénalité (jeter certains de vos autres codes hors du cache du cpu), parfois le "bénéfice" D'AVX et al sera compensé par l'impact qu'il aura sur le reste de votre code. Dépend de ce que vous faites.

vous demandez aussi:

pourquoi la bande passante est-elle si basse avec REP MOVSB? Que puis-je faire pour l'améliorer?

il ne sera pas possible de" faire quelque chose " pour faire fonctionner REP MOVSB plus rapidement. C'est ce qu'il fait.

si vous voulez les vitesses plus élevées que vous voyez de memcpy, vous pouvez creuser la source pour elle. Il est là, quelque part. Ou vous pouvez le tracer à partir d'un débogueur et voir les chemins de code réels pris. Mon espoir est que il utilise certaines de ces instructions AVX pour travailler avec 128 ou 256bits à la fois.

ou vous pouvez juste... Eh bien, vous nous avez demandé de ne pas le dire.

7
répondu David Wohlferd 2017-04-20 09:08:45

Ce n'est pas une réponse à la question posée(s), seulement mes résultats (et des conclusions personnelles) lorsque vous essayez de trouver.

en résumé: GCC optimise déjà memset() / memmove() / memcpy() (voir par exemple gcc/config/i386/i386.c: expand_set_or_movmem_via_rep () dans les sources GCC; aussi chercher stringop_algs dans le même fichier pour voir les variantes dépendantes de l'architecture). Donc, il n'y a aucune raison de s'attendre à des gains massifs en utilisant votre propre variante avec GCC (sauf si vous avez oublié des choses importantes comme les attributs d'alignement pour vos données alignées, ou ne permettent pas des optimisations suffisamment spécifiques comme -O2 -march= -mtune= ). Si vous êtes d'accord, alors les réponses à cette question sont plus ou moins pertinent dans la pratique.

(je ne souhaite qu'il y ait un memrepeat() , à l'opposé de memcpy() par rapport à memmove() , qui reproduirait la partie initiale d'un tampon à remplir la totalité de la mémoire tampon.)


j'ai actuellement une machine de Pont Ivy en service (Ordinateur Portable Core i5-6200U, noyau Linux 4.4.0 x86-64, avec erms dans /proc/cpuinfo drapeaux). Parce que je voulais savoir si je pouvais trouver un cas où une variante memcpy() personnalisée basée sur rep movsb surpasserait une simple memcpy() , j'ai écrit un benchmark trop compliqué.

l'idée de base est que le programme principal affecte trois grands domaines de mémoire: original , current , et correct , exactement de la même taille, et au moins la page-alignés. Les opérations de copie sont groupées en ensembles, chaque ensemble ayant des propriétés distinctes, comme toutes les sources et les cibles étant alignées (sur un certain nombre d'octets), ou toutes les longueurs étant dans la même plage. Chaque ensemble est décrit à l'aide d'un tableau de src , dst , n triplets, où tous les src à src+n-1 et dst à dst+n-1 sont entièrement dans le current zone.

Un Xorshift* GÉNÉRATEUR est utilisé pour initialiser original à données aléatoires. (Comme je l'ai dit plus haut, c'est trop compliqué, mais je voulais m'assurer de ne pas laisser de raccourcis faciles pour le compilateur.) La zone correct est obtenue en commençant par original données dans current , en appliquant tous les triplets dans l'ensemble actuel, en utilisant memcpy() fourni par la bibliothèque C, et en copiant la zone current à correct . Cela permet à chaque fonction référencée d'être vérifiée pour se comporter correctement.

chaque jeu d'opérations de copie est chronométré un grand nombre de fois en utilisant la même fonction, et la médiane de ceux-ci est utilisée pour la comparaison. (À mon avis, la médiane a le plus de sens dans le benchmarking, et fournit la sémantique raisonnable -- la fonction est au moins que rapide au moins la moitié du temps.)

pour éviter les optimisations de compilateurs, j'ai le programme charge le fonctions et points de repère de façon dynamique, au moment de l'exécution. Les fonctions ont toutes la même forme, void function(void *, const void *, size_t) -- notez que contrairement à memcpy() et memmove() , elles ne renvoient rien. Les benchmarks (ensembles nommés d'opérations de copie) sont générés dynamiquement par un appel de fonction (qui prend le pointeur vers la zone current et sa taille comme paramètres, entre autres).

malheureusement, je n'ai pas encore trouvé d'ensemble où

static void rep_movsb(void *dst, const void *src, size_t n)
{
    __asm__ __volatile__ ( "rep movsb\n\t"
                         : "+D" (dst), "+S" (src), "+c" (n)
                         :
                         : "memory" );
}

battrait

static void normal_memcpy(void *dst, const void *src, size_t n)
{
    memcpy(dst, src, n);
}

en utilisant gcc -Wall -O2 -march=ivybridge -mtune=ivybridge en utilisant GCC 5.4.0 sur l'ordinateur portable i5-6200U mentionné ci-dessus qui exécute un noyau linux-4.4.0 64 bits. La copie de morceaux de 4096 octets alignés et dimensionnés est toutefois proche.

cela signifie qu'au moins jusqu'à présent, je n'ai pas trouvé de cas où l'utilisation d'une variante rep movsb memcpy aurait du sens. Il n'y a pas de tels cas; je n'en ai pas trouvé un.

(à ce point le code est un spaghetti mess je suis plus honteux que fier de, donc je vais omettre de publier les sources à moins que quelqu'un demande. La description ci-dessus devrait être suffisant pour écrire mieux, cependant.)


cela ne me surprend pas beaucoup, cependant. Le compilateur C peut déduire beaucoup d'informations sur l'alignement de l'opérande pointeurs, et si le nombre d'octets à copier est une constante de compilation, d'un multiple d'une puissance de deux. Cette information peut, et sera / devrait être utilisé par le compilateur pour remplacer les fonctions de la bibliothèque C memcpy() / memmove() par les siennes.

GCC fait exactement cela (voir par exemple gcc/config/i386/i386.c: expand_set_or_movmem_via_rep () dans les sources GCC; aussi chercher stringop_algs dans le même fichier pour voir les variantes dépendantes de l'architecture). En effet., memcpy() / memset() / memmove() a déjà été optimisé séparément pour un certain nombre de variantes de processeurs x86; il je serais très surpris si les développeurs de GCC n'avaient pas déjà inclus le support erms.

GCC fournit plusieurs attributs de fonction que les développeurs peuvent utiliser pour assurer le bon code généré. Par exemple, alloc_align (n) indique à GCC que la fonction renvoie une mémoire alignée au moins sur les octets n . Une application ou une bibliothèque peut choisir l'implémentation d'une fonction à utiliser au moment de l'exécution, en créant une "fonction de résolveur" (qui renvoie une pointeur de fonction), et définissant la fonction en utilisant l'attribut ifunc (resolver) .

L'un des modèles les plus courants que j'utilise dans mon code est

some_type *pointer = __builtin_assume_aligned(ptr, alignment);

ptr est un pointeur, alignment est le nombre d'octets auxquels il est aligné; GCC sait alors/suppose que pointer est aligné sur alignment octets.

un autre intégré utile, bien que beaucoup plus difficile à utiliser correctement , est __builtin_prefetch() . Pour maximiser la largeur de bande/l'efficacité globale, j'ai trouvé que minimiser les latences dans chaque sous-opération donne les meilleurs résultats. (Pour copier des éléments dispersés dans un stockage temporaire consécutif, c'est difficile, car le pré-découpage implique généralement une ligne de cache complète; si trop d'éléments sont pré-découpés, la plupart du cache est gaspillé en stockant des éléments inutilisés.)

6
répondu Nominal Animal 2017-04-22 13:36:47

il existe des moyens beaucoup plus efficaces de déplacer des données. Ces jours-ci, la mise en œuvre de memcpy va générer du code spécifique à l'architecture à partir du compilateur qui est optimisé sur la base de l'alignement de la mémoire des données et d'autres facteurs. Cela permet une meilleure utilisation des instructions de cache Non temporelles et des XMM et autres registres dans le monde x86.

quand vous hard-code rep movsb empêche cette utilisation d'intrinsèques.

donc, pour quelque chose comme un memcpy , sauf si vous écrivez quelque chose qui sera lié à un morceau très spécifique de matériel et à moins que vous allez prendre le temps d'écrire une fonction memcpy hautement optimisée dans l'assemblage (ou en utilisant des intrinsèques de niveau C), vous êtes far mieux de permettre au compilateur de le comprendre pour vous.

3
répondu David Hoelzer 2017-04-11 10:57:19

en général memcpy() guide:

a) Si les données copiées sont minuscules (moins de 20 octets) et ont une taille fixe, laissez le compilateur le faire. Raison: le compilateur peut utiliser les instructions normales mov et éviter les frais généraux de démarrage.

b) Si les données copiées sont petites (moins de 4 KiB) et sont garanties alignées, utilisez rep movsb (si ERMSB est supporté) ou rep movsd (si ERMSB n'est pas supporté). Raison: À L'Aide De une alternative SSE ou AVX a une énorme quantité de" frais généraux de démarrage " avant de copier quoi que ce soit.

c) si les données copiées sont petites (moins de 4 KiB) et ne sont pas garanties d'être alignées, utiliser rep movsb . Raison: L'utilisation de SSE ou AVX, ou en utilisant rep movsd pour la majeure partie de celui-ci plus certains rep movsb au début ou à la fin, a trop de frais généraux.

D) pour tous les autres cas, utilisez quelque chose comme ceci:

    mov edx,0
.again:
    pushad
.nextByte:
    pushad
    popad
    mov al,[esi]
    pushad
    popad
    mov [edi],al
    pushad
    popad
    inc esi
    pushad
    popad
    inc edi
    pushad
    popad
    loop .nextByte
    popad
    inc edx
    cmp edx,1000
    jb .again

raison: Cela sera si lent qu'il forcera les programmeurs à trouver une alternative qui n'implique pas la copie de globs énormes de données; et le logiciel résultant sera beaucoup plus rapide parce que la copie de globs grands de données a été évitée.

1
répondu Brendan 2017-04-20 11:28:51