Comment puis-je faire manquer un cache d'instructions?
J'ai été chargé de générer un certain nombre de données, le cache et l'instruction-les défauts de cache. J'ai été capable de gérer la mémoire cache de données partie sans problème.
Donc je suis à gauche avec la génération de l'instruction-les défauts de cache. Je n'ai aucune idée de ce que sont les causes de ces. Quelqu'un peut-il suggérer une méthode de génération?
Je suis en utilisant GCC sous Linux.
source d'informationauteur William the Pleaser | 2012-05-14
Vous devez vous connecter pour publier un commentaire.
Comme les gens l'ont expliqué, un cache d'instructions miss est conceptuellement les mêmes données dans le cache - les instructions ne sont pas dans le cache. C'est parce que le processeur du compteur de programme (PC) a bondi à un endroit qui n'a pas été chargé dans le cache, ou qui a été éliminée parce que le cache s'est rempli, et que la ligne de cache a été choisie pour l'expulsion (généralement la moins récemment utilisée).
Il est un peu plus difficile à générer assez de code à la main à force, une instruction de la miss que c'est à force de données dans le cache.
Une façon d'obtenir beaucoup de code, pour peu d'effort, est d'écrire un programme qui génère le code source.
Par exemple écrire un programme pour générer une fonction avec un énorme instruction switch (C) [Avertissement, non testé]:
Alors vous pouvez appeler à partir d'une autre fonction, et vous pouvez contrôler la taille d'un saut le long de la ligne de cache qu'il faut.
Une propriété d'une instruction switch est le code peut être contraint à exécuter en arrière, ou dans des modèles en choisissant le paramètre. De sorte que vous pouvez travailler avec le pré-chargement des pages et la prédiction des mécanismes, ou d'essayer de travailler contre eux.
La même technique pourrait être appliquée à générer beaucoup de fonctions, pour s'assurer que le cache peut être "cassé" à volonté. Ainsi, vous pouvez avoir bigswitch001, bigswitch002, etc. On pourrait appeler cela l'aide d'un interrupteur qui vous génèrent également.
Si vous pouvez le faire chaque fonction (environ) d'un certain nombre de lignes de cache de taille, et aussi de générer plus de fonctions que ne le fit dans le cache, alors le problème de la génération de cache d'instructions de justesse devient plus facile à contrôler.
Vous pouvez voir exactement comment grand d'une fonction, d'une instruction switch, ou chaque jambe d'une instruction switch est par le dumping de l'assembleur (à l'aide de gcc-S), ou objdump l' .o fichier. Vous pourriez donc à une "harmonie" de la taille d'une fonction en ajustant le nombre de
case:
consolidés. Vous pouvez également choisir le nombre de lignes de cache sont touchés, par un choix judicieux du paramètre à bigswitchNNN().En plus de tous les autres moyens mentionnés ici, une autre très bonne façon de forcer un cache d'instructions miss est d'avoir l'auto-la modification de code.
Si vous écrivez une page de code dans la mémoire (en supposant que vous avez configuré le système d'exploitation le permettent), puis, bien sûr, la ligne de cache d'instructions immédiatement devient invalide, et le processeur est forcé à réextraire.
Il n'est pas de la branche prédiction qui provoque une icache manquer, par la manière, mais simplement ramification. Vous manquez cache d'instructions chaque fois que le processeur tente d'exécuter une instruction qui n'a pas encore été exécuté. Moderne x86 est assez intelligent pour extraire les instructions dans la séquence, de sorte que vous êtes très peu de chances de rater icache par les ordinaires de la marche en avant à partir d'une instruction à l'autre. Mais de toute branche (conditionnelle ou non) permet de passer à une nouvelle adresse de la séquence. Si la nouvelle adresse d'instruction n'a pas été exécuté récemment, et n'est pas près de le code que vous étiez déjà en cours d'exécution, il est susceptible d'être de cache et le processeur doit s'arrêter et attendre les instructions de RAM principale. C'est exactement comme cache de données.
Certains très processeurs modernes (récent i7) sont en mesure de regarder à venir branches dans le code et commencer à le icache de pré-chargement les cibles possibles, mais beaucoup ne peuvent pas (consoles de jeux vidéo). De l'extraction de données à partir de RAM principale de icache est totalement différente de l'instruction de chercher" étape du pipeline, qui est la branche prédiction est d'environ.
"Instruction fetch" fait partie de la CPU de l'exécution du pipeline, et se réfère à l'introduction de l'opcode de icache dans la CPU de l'unité d'exécution, où il peut commencer le décodage et de faire le travail. C'est différent de "cache d'instructions" chercher, ce qui doit arriver au nombre de cycles plus tôt et implique le cache circuits de faire une demande à la mémoire principale de l'unité d'envoyer des octets à travers le bus. La première est une interaction entre deux étapes de la CPU pipeline. La deuxième est une interaction entre le gazoduc et la mémoire cache et la mémoire principale, qui est beaucoup plus compliqué que la pièce de circuits. Les noms sont très semblables, mais ils sont complètement distinctes.
Donc une autre façon de causer un cache d'instructions manque serait d'écrire (ou générer) beaucoup de très grandes fonctions, de sorte que votre segment de code est énorme. Appelez ensuite sauvagement d'une fonction à l'autre, de sorte qu'à partir de la CPU, du point de vue de vous faire des folies GOTOs tous sur la mémoire.
Votre projet nécessite une prise de conscience de votre cible du système de cache de matériel, y compris mais non limité à la taille du cache (l'ensemble de la taille de la mémoire cache), le cache de la ligne de taille (la plus petite entité pouvant être mis en cache), associativité, et écrire & politiques en matière de remplacement. Tout vraiment bon algorithme conçu pour tester un cache de performance doit prendre tout cela en compte, car il n'y a pas un seul algorithme général qui permettrait de tester toutes les configurations de cache, si vous pouvez être en mesure de concevoir un efficace paramétré test de routine générateur, ce qui pourrait générer un test de routine donné assez de détails sur une cible donnée par le cache de l'architecture. Malgré cela, je pense que ma proposition ci-dessous est un très bon général-cas test, mais d'abord je voulais mentionner:
Vous mentionner que vous avez un travail de cache de données de test qui utilise un “grand tableau d'entiers[100].... [accès] les éléments de telle manière que la distance entre les deux éléments est plus grand que le cache de la ligne de taille(32 octets dans mon cas).” Je suis curieux de savoir comment vous avez déterminé que votre test de l'algorithme fonctionne et comment vous avez déterminé le nombre de données de cache sont le résultat de votre algorithme, par opposition à la manque causé par d'autres stimuli. En effet, avec un test de tableau de 100*sizeof(int), vos données de test de la zone est à seulement 400 octets de long sur plus d'usage général, de plates-formes d'aujourd'hui (peut-être 800 octets si vous êtes sur une plate-forme 64 bits, ou 200 octets si vous utilisez un 16 bits plate-forme). Pour la grande majorité de cache architectures, que l'ensemble de l'examen de la matrice de tenir dans le cache plusieurs fois, ce qui signifie que randomisés accède au tableau apportera l'ensemble de la matrice dans le cache en quelque part autour de (400/cache_line_size)*2 accès, et tous les accès après ce sera un cache hit indépendamment de la façon dont vous afin vos accès, à moins que certains matériels ou des OS timer tick interrupt pop dans la récupération et élimination de certaines ou de l'ensemble de vos données en cache.
À l'égard du cache d'instructions: d'Autres ont suggéré l'utilisation d'un grand switch () instruction de cas ou la fonction des appels à des fonctions dans des endroits différents, ni de ce qui serait, de manière prévisible, efficace sans attentivement (et je veux BIEN) de la conception de la taille du code, dans le cas des succursales ou des lieux & tailles de la disparately-situé fonctions. La raison pour cela est que les octets tout au long de la mémoire “pli” (techniquement, “alias l'un de l'autre”), le cache dans un tout schéma prévisible. Si vous soigneusement contrôler le nombre d'instructions dans chaque branche d'un switch () instruction de cas, vous pourriez être en mesure d'aller quelque part avec votre analyse, mais, si vous venez de lancer un grand aveugle montant des instructions dans chacun des cas, vous n'avez aucune idée de comment ils vont se replier dans le cache et la cas de l'interrupteur () instruction de cas alias les uns des autres afin de les utiliser pour expulser les uns les autres de la cache.
Je devine que vous n'êtes pas trop familier avec le code d'assemblée, mais vous avez obtenu d'croyez-moi, ici, ce projet est en train de crier. Croyez-moi, je ne suis pas à utiliser le code d'assemblée où il n'est pas nécessaire, et je préfère largement la programmation OO C++, en utilisant STL & polymorphes ADT hiérarchies chaque fois que possible. Mais dans votre cas, il n'y a vraiment pas d'autre moyen infaillible de le faire, et l'assemblée se vous donner le contrôle absolu sur le code des tailles de bloc que vous avez vraiment besoin pour être en mesure de générer efficacement spécifié taux d'accès au cache. Vous n'auriez pas à devenir une assemblée d'experts, et vous avez probablement woudn même pas besoin d'apprendre les instructions & structure nécessaires pour mettre en œuvre un langage C prologue & épilogue (Google pour “C-callable assemblée fonction”). Vous écrivez extern “C” prototypes de fonction pour votre assemblage de fonctions, et vous allez loin. Si vous tenez à en savoir un peu de montage, plus de la logique du test de vous mettre dans l'assemblée des fonctions, moins de “Heisenberg” effet de vous imposer dans votre test, puisque vous pouvez contrôler attentivement où le test instructions de contrôle d'aller (et donc leur effet sur le cache d'instructions). Mais pour la majeure partie de votre code de test, vous pouvez simplement utiliser un tas de “nop” instructions (le cache d'instructions n'a pas vraiment attention à ce que les instructions qu'il contient), et probablement il suffit de mettre votre processeur du "retour" de l'enseignement au bas de chaque bloc de code.
Maintenant, imaginons que votre cache d'instructions est de 32 ko (vachement petit par les normes d'aujourd'hui, mais peut-être encore courante dans de nombreux systèmes embarqués). Si la mémoire cache est de 4-way associatif, vous pouvez créer huit contenu identique 8K assemblée des fonctions (qui nous l'espérons vous remarqué, c'est de 64 ko valeur de code, deux fois la taille de la mémoire cache), dont la majeure partie est juste un tas d'instructions NOP. Vous les faire tous tomber les uns après les autres dans la mémoire (généralement par tout simplement la définition de chacun l'un après l'autre dans le fichier source). Vous pouvez ensuite appeler à partir d'un test de contrôle de la fonction en utilisant soigneusement calculée séquences de générer un cache hit ratio que vous désirez (avec plutôt sûr de granularité étant donné que les fonctions sont chacun un plein 8K de long). Si vous appelez la 1ère, 2ème, 3ème, et 4ème fonctions de l'un après l'autre, vous savez que vous avez rempli l'intégralité du cache avec ceux des fonctions de test de code". Appeler à l'un de ceux encore à ce stade, n'entraînera pas un cache d'instructions manquer (à l'exception des lignes expulsés par le test de contrôle de la fonction d'instructions), mais l'appel de l'un de l'autre (5ème, 6ème, 7ème ou 8ème; disons simplement choisir le 5e) de l'expulsion de l'un des autres (mais qui est expulsé dépend de votre cache de la politique de remplacement). À ce stade, le seul que vous pouvez appeler et de savoir que vous n'aurez PAS expulser un autre est celui qui vient d'être appelé (le 5e), et les seuls que vous pouvez appeler et de savoir que tu VA expulser un autre en est une que vous n'avez pas encore appelé (6e, 7e ou 8e). Pour rendre cela plus facile, il suffit de maintenir un tableau statique de taille moyenne le même que le nombre de fonctions de test que vous avez. Pour déclencher une expulsion, appeler la fonction à la fin de la table & déplacez le pointeur vers le haut du tableau, en déplaçant les autres. Pour ne PAS déclencher une expulsion, appelez l'un de vous a le plus récemment appelé (l'un au-dessus de la matrice; assurez-vous de ne PAS décaler les autres vers le bas dans ce cas!). Faire des variations sur cette (peut-être a-16 4K assemblée des fonctions) si vous avez besoin d'une granularité plus fine. Bien sûr, tout cela repose sur le test de la logique de contrôle de la taille insignifiante en comparaison à la taille de chaque associatif “chemin” de la cache; pour plus de contrôle positif, vous pourriez mettre à l'essai de logique de contrôle dans les fonctions de test d'eux-mêmes, mais pour un contrôle parfait, vous auriez à la conception de la logique de contrôle entièrement dénuée de ramification (seulement de branchement à la fin de chaque session de l'assemblée de la fonction), mais je pense que je vais arrêter là puisque c'est probablement trop compliquer les choses.
Hors-la-brassard & pas testé, l'intégralité de l'un de l'assemblée des fonctions pour x86 pourrait ressembler à ceci:
Pour PowerPC, il pourrait ressembler à ceci (également non testé):
Dans les deux cas, le C++ et C prototypes pour l'appel à ces fonctions seraient les suivantes:
En fonction de votre compilateur, vous pourriez avoir besoin d'un underscore devant le nom de la fonction dans le code de l'assemblée elle-même (mais pas en C++/C prototype de fonction).
Pour l'instruction cache, vous devez exécuter du code des segments qui sont éloignés. La division de votre logique entre plusieurs appels de fonction serait une façon de le faire.
Une chaîne de si d'autre sur les conditions imprévisibles (par exemple, apport ou de données généré aléatoirement) avec la quantité d'instructions à la fois dans les cas et dans l'autre cas, dont la taille est supérieure à une ligne de cache.