5 votes

Comment utiliser systématiquement le logiciel prefetch ?

Après avoir lu la réponse acceptée dans Quand doit-on utiliser le prefetch ? et des exemples de Exemples de préemption ? J'ai encore beaucoup de mal à comprendre quand il faut vraiment utiliser prefetch. Bien que ces réponses fournissent un exemple où le prefetch est utile, elles n'expliquent pas comment le découvrir dans des programmes réels. Cela ressemble à des suppositions aléatoires.

En particulier, je suis intéressé par les implémentations C pour intel x86 (prefetchnta, prefetcht2, prefetcht1, prefetcht0, prefetchw) qui sont accessibles par l'intermédiaire de GCC __builtin_prefetch intrinsèque. J'aimerais savoir :

  • Comment puis-je voir que le logiciel prefetch peut m'aider pour mon programme spécifique ? J'imagine que je peux collecter des mesures de profilage du CPU (par exemple, le nombre de manques de cache) avec Intel Vtune ou un utilitaire Linux. perf . Dans ce cas, quelles sont les mesures (ou la relation entre elles) qui indiquent la possibilité d'améliorer les performances avec un logiciel de préextraction ?
  • Comment puis-je localiser les charges qui souffrent le plus des manques de cache ?
  • Comment voir le niveau de cache où les ratés se produisent pour décider quel prefetch(0,1,2) utiliser ?
  • En supposant que j'ai trouvé une charge particulière qui souffre d'un manque dans un niveau de cache spécifique, où dois-je placer le prefetch ? À titre d'exemple, supposons que la boucle suivante souffre de manques de cache

    for (int i = 0; i < n; i++) { // some code double x = a[i]; // some code }

Dois-je placer la prélecture avant ou après le chargement ? a[i] ? Jusqu'où doit-il aller ? a[i+m] ? Dois-je m'inquiéter de dérouler la boucle pour m'assurer que je ne fais de la préextraction que sur les limites des lignes de cache ou cela sera-t-il presque gratuit comme une nop si les données sont déjà dans le cache ? Est-il utile d'utiliser plusieurs __builtin_prefetch d'affilée pour préempter plusieurs lignes de cache à la fois ?

3voto

Jérôme Richard Points 7521

Comment puis-je voir que le logiciel prefetch peut m'aider pour mon programme spécifique ?

Vous pouvez vérifier la proportion d'échecs de cache. perf ou VTune peut être utilisé pour obtenir cette information grâce à compteurs de performance matérielle . Vous pouvez obtenir la liste avec perf list par exemple. La liste dépend de l'architecture du processeur cible, mais il existe des événements génériques. Par exemple, L1-dcache-load-misses , LLC-load-misses y LLC-store-misses . Il n'est pas très utile de connaître le nombre de ratés du cache si l'on ne dispose pas également du nombre de chargements/stockages. Il existe des compteurs génériques comme L1-dcache-loads , LLC-loads o LLC-stores . AFAIK, pour la L2, il n'y a pas de compteurs génériques (au moins sur les processeurs Intel) et vous devez utiliser des compteurs matériels spécifiques (par exemple l2_rqsts.miss sur les processeurs Intel de type Skylake). Pour obtenir les statistiques globales, vous pouvez utiliser perf stat -e an_hardware_counter,another_one your_program . Une bonne documentation peut être trouvée aquí .

Lorsque la proportion de ratés est importante, vous devez essayer d'optimiser le code, mais ce n'est qu'une indication. En fait, en ce qui concerne votre application, vous pouvez avoir beaucoup d'occurrences dans le cache mais beaucoup d'absences dans le cache dans les parties/temps critiques de votre application. Par conséquent, les ratés du cache peuvent être perdus parmi tous les autres. Cela est particulièrement vrai pour les références de cache L1 qui sont massives dans les codes scalaires par rapport aux codes SIMD. Une solution consiste à ne profiler qu'une partie spécifique de votre application et à utiliser la connaissance de celle-ci pour enquêter dans la bonne direction. Les compteurs de performance ne sont pas vraiment un outil pour rechercher automatiquement les problèmes dans votre programme, mais un outil pour vous aider à valider/défaire une certaine hypothèse ou à donner quelques conseils sur ce qui se passe. Il vous donne des preuves pour résoudre une affaire mystérieuse mais c'est à vous, le détective, de faire tout le travail.

Comment puis-je localiser les charges qui souffrent le plus des manques de cache ?

Certains compteurs de performances matérielles sont " précis ", ce qui signifie que l'instruction qui a généré l'événement peut être localisée. Il s'agit très utile puisque vous pouvez savoir quelles instructions sont responsables de la plupart des manques de cache (bien que ce ne soit pas toujours précis en pratique). Vous pouvez utiliser perf record + perf report afin d'obtenir les informations (voir le tutoriel précédent pour plus d'informations).

Notez que il y a beaucoup de raisons qui peuvent causer un manque de cache et seulement quelques cas peuvent être résolus en utilisant le logiciel prefetching .

Comment voir le niveau de cache où les ratés se produisent pour décider quel prefetch(0,1,2) utiliser ?

Ce choix est souvent difficile à faire dans la pratique et dépend beaucoup de votre application. Théoriquement, le nombre est un indice pour indiquer au processeur si le niveau de localité de la ligne de cache cible (par exemple, récupérée dans le cache L1, L2 ou L3). Par exemple, si vous savez que des données doivent être lues et réutilisées prochainement, il est judicieux de les placer dans le L1. Cependant, si le L1 est utilisé et que vous ne voulez pas le polluer avec des données utilisées une seule fois (ou rarement utilisées), il est préférable de récupérer les données dans des caches inférieurs. En pratique, c'est un peu complexe car le comportement peut ne pas être le même d'une architecture à l'autre... Voir Quels sont les _mm_prefetch() des indications sur la localité ? pour plus d'informations.

Un exemple d'utilisation est pour cette question . Le préfixe logiciel a été utilisé pour éviter le problème de destruction de la mémoire cache avec certaines étapes spécifiques. C'est un cas pathologique où le prefetcher matériel n'est pas très utile.

En supposant que j'ai trouvé une charge particulière qui souffre d'un manque dans un niveau de cache spécifique, où dois-je placer le prefetch ?

C'est clairement la partie la plus délicate. Vous devez préempter les lignes de cache suffisamment tôt pour que la latence soit réduite de manière significative, sinon l'instruction est inutile et l'utilisateur n'a pas le temps de la lire. peut en fait être préjudiciable . En effet, l'instruction prend de la place dans le programme, doit être décodée, et utilise des ports de charge qui pourraient être utilisés pour exécuter d'autres instructions de charge (plus critiques) par exemple. Cependant, s'il est trop tard, alors la ligne de cache peut être évincée et doit être rechargée...

La solution habituelle consiste à écrire un code comme celui-ci :

for (int i = 0; i < n; i++) {
   // some code
   const size_t magic_distance_guess = 200;
   __builtin_prefetch(&data[i+magic_distance_guess]);
   double x = a[i];
   // some code
}

magic_distance_guess est une valeur généralement fixée sur la base de critères de référence (ou d'une compréhension très approfondie de la plate-forme cible, bien que la pratique montre souvent que même les développeurs hautement qualifiés ne parviennent pas à trouver la meilleure valeur).

Le truc c'est que la latence dépend beaucoup de d'où proviennent les données et le plateforme cible . Dans la plupart des cas, les développeurs ne peuvent pas savoir exactement à quel moment effectuer la préextraction, à moins qu'ils ne travaillent sur une plateforme cible unique. . Cela rend la préextraction logicielle délicate à utiliser et souvent préjudiciable lorsque la plate-forme cible change (il faut tenir compte de la maintenabilité du code et de la surcharge de l'instruction). Sans oublier que les built-ins dépendent du compilateur, que les intrinsèques de la préextraction dépendent de l'architecture et qu'il y a des différences entre les deux. pas de moyen standard et portable d'utiliser la préextraction logicielle .

Dois-je m'inquiéter de dérouler la boucle pour m'assurer que je ne prélivre que sur les limites des lignes de cache ou cela sera presque gratuit comme un nop si les données sont déjà dans le cache ?

Oui, les instructions de préextraction ne sont pas gratuites et il est donc préférable d'utiliser une seule instruction par ligne de cache (car les autres instructions de préextraction sur la même ligne de cache seront inutiles).

Est-il utile d'utiliser plusieurs appels __builtin_prefetch à la suite pour préempter plusieurs lignes de cache à la fois ?

Cela dépend beaucoup de la plate-forme cible. Les processeurs x86-64 grand public modernes exécutent les instructions en parallèle, sans ordre, et disposent d'une fenêtre d'analyse des instructions assez large. Ils ont tendance à exécuter la charge le plus tôt possible afin d'éviter les ratés et ils sont souvent très bons pour ce travail.

Dans votre exemple de boucle, je m'attends à le prefetcher matériel devrait faire un très bon travail et utiliser le prefetching logiciel devrait être plus lent sur un processeur grand public (relativement récent).


La préextraction logicielle était utile lorsque les préextracteurs matériels n'étaient pas très intelligents il y a dix ans, mais ils ont tendance à être très bons aujourd'hui. De plus, il est souvent préférable de guider les préempteurs matériels que d'utiliser les instructions de préemption logicielles, car les premiers ont une surcharge moins importante. C'est pourquoi la préextraction logicielle est déconseillée (par exemple par Intel et la plupart des développeurs), sauf si vous savez vraiment ce que vous faites. .

2voto

chqrlie Points 17105

Comment utiliser systématiquement le logiciel prefetch ?

La réponse rapide est : Ne le fais pas. .

Comme vous l'avez correctement analysé, la préextraction est une technique d'optimisation délicate et avancée qui n'est pas portable et rarement utile.

Vous pouvez utiliser le profilage pour déterminer quelles sections du code constituent un goulot d'étranglement et utiliser des outils spécialisés tels que Valgrind pour essayer d'identifier les manques de cache qui pourraient potentiellement être évités en utilisant des compilateurs intégrés.

N'en attendez pas trop, mais profilez le code pour concentrer vos efforts d'optimisation là où cela peut être utile.

N'oubliez pas non plus qu'un meilleur algorithme peut battre une mise en œuvre optimisée d'un algorithme moins efficace pour les grands ensembles de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X