La topologie processeur

Envoyer un nouvel article

April 13, 2009 4:00 PM PDT


Les informations sur la topologie processeur sont importantes pour un certain nombre de pratiques de la gestion des ressources de traitement, de l’ordonnancement des tâches ou des threads jusqu’au contrôle ou à la migration des affinités en passant par le respect des licences logicielles. Cet article aborde les algorithmes d’énumération topologique pour les plates-formes avec une ou plusieurs interfaces matérielles d’échange de données (« mono-socket » ou « multi-socket ») équipées de processeurs de la technologie Intel® 64 ou IA-32. Les algorithmes d’énumération topologique (topologie processeur et topologie cache) qui exploitent l’identifiant APIC ont été élargis à l’utilisation de l’identifiant x2APIC, sachant que ce dernier sera nécessaire aux futures plates-formes gérant plus de deux cent cinquante-six processeurs dans un domaine cohérent.

Version PDF

Cet article est également disponible en téléchargement. [PDF de 153 ko]

Paquetage de code

Fichier téléchargeable topology_enumeration_08062008.zip [ZIP de 40 ko]

Introduction

Depuis quelques années, les processeurs ont quasiment généralisé le multithreading (parallélisme de traitement) et la majorité de ceux vendus par Intel aujourd’hui prennent en charge ce type de traitement, sous une forme ou sous une autre (multicœur et/ou multithreading simultané [Simultaneous Multi-Threading, SMT], cette seconde méthode correspondant à la technologie Hyper-Threading d’Intel, lancée en 2002). Du point de vue matériel, le paquet physique que constitue une puce d’architecture Intel 64 gère aussi bien le SMT que le multicœur. Par conséquent, un processeur physique est, en pratique, un ensemble hiérarchisé de processeurs logiques qui partagent certaines ressources système (mémoire, bus/liens système et caches, par exemple). Au niveau de la plate-forme matérielle, le multithreading matériel mis en œuvre sur une configuration multiprocesseur est le fait d’au moins deux processeurs physiques, en configuration uniforme ou non par rapport au sous-système mémoire.

Le génie logiciel qui exploite ce type de traitement doit obéir aux modèles de programmation et aux ressources logicielles fournies par le système d’exploitation sous-jacent (OS). Par exemple, l’ordonnanceur d’un OS affecte en général une tâche logicielle à partir d’une file d’attente, à l’aide d’une ressource matérielle à la granularité d’un processeur logique. Un OS peut définir sa propre structure de données et fournir aux applications des services qui leur permettent d’individualiser l’affectation entre tâche et processeur logique par l’intermédiaire d’une structure d’affinité pour les applications multithreads. L’OS et la pile logicielle sous-jacente à une application (le BIOS, le chargeur d’OS) jouent eux aussi un rôle important dans la mise en œuvre des capacités fonctionnelles du parallélisme matériel et dans la configuration des ressources logicielles définies par l’OS.

L’instruction CPUID de la technologie Intel 64 définit un riche ensemble d’informations qui assiste le BIOS, l’OS et les applications pour l’interrogation des informations topologiques processeur qui sont nécessaires au bon fonctionnement de chaque couche de la pile logicielle. En général, le BIOS doit rassembler les informations topologiques d’un processeur physique, déterminer combien de ces processeurs sont présents dans la configuration, préparer les ressources logicielles nécessaires en fonction de cette topologie et transmettre les informations topologiques système à la couche de la pile logicielle qui reprend ensuite le contrôle du système. L’OS et les couches applicatives exploitent ces informations topologiques à des multiples niveaux. Cet article aborde ainsi plusieurs des usages logiciels les plus courants, par l’OS et les applications, de l’instruction CPUID pour analyser la topologie du ou des processeurs d’une configuration.

Le principal usage logiciel de cette énumération topologique est l’interrogation et l’identification de la relation hiérarchique entre les processeurs logiques, les cœurs d’exécution et la ou les puces physiques d’une configuration. C’est à cet usage que renvoie ici la locution énumération topologique système. Elle peut être nécessaire à un OS ou à certaines applications pour la mise en œuvre de règles de respect des licences logicielles en fonction des processeurs physiques. Elle est également employée par l’OS pour bien ordonnancer les tâches, limiter la migration des threads, configurer les interfaces de gestion des threads applicatifs et configurer les services d’affectation de la mémoire en fonction de la topologie mémoire/processeur. Les applications parallélisées en ont besoin pour déterminer le liaisonnement optimal des threads (thread binding), gérer les affectations mémoire pour une localité optimale et améliorer le dimensionnement des performances sur les configurations multiprocesseurs.

Les processeurs de technologie Intel 64 répondent aux impératifs d’énumération topologique système :

  1. A chacun des processeurs logiques d’une plate-forme Intel 64 ou IA-32 qui gère la mémoire cohérente est attribué un identifiant qui lui est propre (APIC ID) dans le domaine cohérent. Une installation en grappe multinœud peut employer un BIOS propriétaire, qui préserve les identifiants APIC attribués (à la réinitialisation du processeur) dans chaque domaine cohérent, et l’étendre par des identifiants de nœuds pour former un sur-ensemble d’identifiants dans la configuration grappée. Cet article se limitera aux interfaces CPUID qui fournissent des identifiants dans un domaine cohérent.
  2. Les valeurs de ces identifiants uniques attribués sur une plate-forme Intel 64 ou IA-32 cohérente obéissent à un algorithme qui s’appuie sur la décomposition du champ de bits de l’identifiant APIC en trois sous-champs. Les trois ensembles de sous-champs correspondent aux trois niveaux hiérarchiques « SMT », « cœur d’exécution » (ou « cœur ») et « paquet physique » (ou « paquet »), ce qui permet de mettre en correspondance chaque niveau hiérarchique avec un sous-champ (sous-identifiant) de l’identifiant APIC.

Du point de vue conceptuel, la fonction d’un algorithme d’énumération topologique consiste simplement à extraire le sous-identifiant correspondant à un niveau hiérarchique donné de l’APIC ID, à partir de deux paramètres qui définissent le sous-ensemble de bits d’un identifiant APIC. Les paramètres concernés sont : (a) le nombre de bits (largeur) d’un masque servant à masquer les bits inutilisés de cet identifiant ; (b) le décalage par rapport au bit 0 de cet identifiant.

Le niveau « SMT » correspond au constituant du plus bas niveau de la topologie processeur. Il est donc situé dans la partie de poids faible de l’identifiant APIC. Si la largeur correspondante est nulle pour « SMT », cela implique que le niveau hiérarchique suivant (« extérieur ») ne comporte qu’un seul processeur logique. Ainsi, la valeur de SMT_Mask_Width pour les processeurs Intel® Core™2 Duo est en général nulle ; si la largeur correspondant est d’un bit, il se peut que le niveau hiérarchique suivant comporte deux processeurs logiques.

Si la largeur correspondante est nulle pour « cœur », il en découle qu’un processeur physique ne compte qu’un seul cœur ; si la largeur correspondante est d’un bit pour « cœur », c’est qu’un processeur physique peut se composer de deux cœurs.

A noter que les valeurs de l’identifiant APIC attribuées sur l’ensemble des processeurs logiques de la configuration n’ont pas besoin d’être contiguës, mais les sous-ensembles des champs de bits correspondant à trois niveaux hiérarchiques sont contigus en bordure de champ de bits. En raison de cet impératif, le décalage de bit du masque pour l’extraction d’un sous-identifiant donné peut s’obtenir à partir de la « largeur de masque » des niveaux hiérarchiques inférieurs (« intérieurs »).

Identifiants APIC individuels en configuration multiprocesseur

Bien que les anciennes configurations multiprocesseurs équipées de puces IA-32 aient attribué des identifiants APIC individuels à chacun de leurs processeurs logiques, les interfaces de programmation ont connu, depuis, plusieurs évolutions. Pour les processeurs Intel® Pentium® Pro et Pentium® III Xeon®, ces identifiants ne sont accessibles qu’à partir des registres APIC locaux (ces registres emploient des interfaces d’E/S projetées en mémoire [memory-mapped] et gérées par l’OS). Pour la première génération des processeurs Intel Pentium 4 et Intel Xeon (2000-2001), c’est l’instruction CPUID qui fournissait des informations sur les identifiants APIC d’origine attribués lors de leur réinitialisation de ces puces. L’instruction CPUID de la première génération des processeurs Intel Xeon MP et Pentium 4 avec gestion de la technologie Hyper-Threading (2002) fournissait des informations supplémentaires, qui permettaient aux logiciels de décomposer les identifiants APIC initiaux en une énumération topologique à deux niveaux. Avec le lancement (2005) des premiers processeurs double cœur de technologie Intel 64, l’énumération topologique système a évolué en un algorithme à trois niveaux sur les identifiants APIC initiaux de huit bits. Les futures plates-formes Intel qui intégreront cette même technologie seront sans doute capables de gérer un grand nombre de processeurs logiques, qui dépassera cette capacité du champ APIC ID initial de 8 bits. L’extension x2APIC de cette technologie définit un identifiant x2APIC sur 32 bits et l’instruction CPUID des futurs processeurs correspondants devrait permettre aux logiciels d’énumérer la topologie système à partir de tels identifiants. La feuille (nœud externe, leaf) d’énumération topologique étendue du CPUID (Leaf 11) est l’interface préférentielle pour l’énumération topologique système des futurs processeurs de technologie Intel 64.

L’instruction CPUID de ces futures puces pourra gérer la feuille 11 indépendamment du matériel x2APIC. Pour une grande partie des plates-formes dotées de cette technologie, l’énumération topologique système pourra s’effectuer soit par CPUID Leaf 11 ou à l’aide de l’identifiant APIC initial (via CPUID Leaf 1 et Leaf 4). La figure 1 illustre un exemple de la manière dont un logiciel peut choisir des informations de feuille CPUID pour obtenir l’énumération topologique système.

Figure 1. Exemple de choix des informations de feuille CPUID pour l’énumération topologique processeur.

La valeur maximale de la feuille CPUID gérée peut se déterminer en donnant à EAX une valeur nulle, en exécutant l’instruction CPUID et en consultant la valeur résultant d’EAX, à savoir CPUID.0:EAX. Si CPUID.0:EAX ≥ 11, le logiciel peut déterminer si CPUID Leaf 11 existe en quantifiant EAX=11 et ECX=0, en exécutant l’instruction CPUID pour consulter la valeur non nulle résultante dans EBX, à savoir CPUID. (EAX=11, ECX=0):EBX != 0.

Pour être totalement fonctionnel, un multithreading matériel impose un signalement complet des feuilles CPUID.

Si le logiciel constate que CPUID.0:EAX < 4 sur un processeur de technologie Intel 64 ou IA-32 récent (sorti après 2004), il doit consulter le MSR IA32_MISC_ENABLES[bit 22].

Si la valeur de celui-ci a été fixée à 1 (par le BIOS, par exemple), on peut obtenir le signalement complet de la fonction de feuille CPUID en faisant attribuer à IA32_MISC_ENABLES[bit 22] une valeur nulle (par Modify BIOS CMO S ou via WRMSR).

L’algorithme d’énumération topologique système à trois niveaux (via CPUID Leaf 1 et Leaf 4) est parfaitement compatible avec les processeurs IA-32 plus anciens, qui ne gèrent qu’une topologie à deux niveaux (« SMT » et « paquet physique »). Les processeurs pour lesquels le résultat de CPUID.1:EBX[23:16] est « réservé » (nul) ne gèrent qu’une topologie à un niveau.

L’extrait de code A-1 en appendice illustre la gestion des fonctions de feuille CPUID pour trois catégories de processeurs physiques.

Enumération topologique système via feuille topologique CPUID étendue

L’algorithme d’énumération topologique système se décompose en trois grandes étapes :

  • Déterminer les constantes de « largeur de masque » qui serviront à l’extraction de chaque sous-identifiant.
  • Obtenir les identifiants APIC propres à chaque processeur logique de la configuration et extraire/décomposer chacun en trois ensembles de sous-identifiants.
  • Analyser la relation hiérarchique des sous-identifiants pour établir des tables de correspondance entre les services de gestion des threads de l’OS en fonction des trois niveaux la topologie processeur.

L’extrait de code A-2 en annexe fournit un exemple de la structure de base de ces trois phases de l’énumération pour les topologies processeur et cache.

La figure 2 ci-dessous indique les procédures d’interrogation CPUID Leaf 11 pour l’identifiant x2APIC et l’extraction des sous-identifiants correspondant aux nivaux hiérarchiques « SMT », « cœur » et « paquet physique ».

Figure 2. Procédures d’extraction des sous-identifiants x2APIC de chaque processeur logique.

L’extrait de code A-3 en appendice liste une structure de données qui comporte les identifiants APIC, divers sous-identifiants et l’ensemble hiérarchique d’un système ordinal d’énumération pour chaque entité de la topologie processeur et/ou cache d’un système.

L’énumération topologique système au niveau applicatif via CPUID suppose l’exécution de cette instruction sur chaque processeur logique de la configuration, ce qui implique un changement de contexte à l’aide de services fournis par un OS. Or le changement de contexte à la demande par code utilisateur s’appuie en général sur une API de gestion de l’affinité des threads fournie par l’OS, et les fonctions ainsi que les limitations de cette API varient d’un OS à l’autre. Pour certains, par exemple, cette API s’assortit d’une limite de trente-deux ou soixante-quatre processeurs logiques. Cependant, ses futures versions devraient s’améliorer pour lui permettre de gérer des processeurs logiques plus nombreux.

Enumération topologique système via CPUID Leaf 1 et Leaf 4

La figure 3 ci-dessous illustre les procédures d’interrogation de l’identifiant APIC initial via CPUID Leaf 1 et l’extraction des sous-identifiants correspondant aux niveaux « SMT », « cœur » et « paquet physique » de la hiérarchie, via CPUID Leaf 1 et Leaf 4. Cette extraction à partir de l’identifiant APIC initial s’effectue par interrogation de CPUID Leaf 1 et 4 pour obtenir le nombre de bits respectif de la sélection des trois masques (Select_Mask : SMT, Core et Pkg) qui composent le champ initial APIC ID de 8 bits. Cette sélection permet aux logiciels d’extraire de l’identifiant APIC de chaque processeur logique les sous-identifiants correspondant aux niveaux « SMT », « cœur » et « paquet physique ».

Figure 3. Procédures d’extraction des sous-identifiants de l’APIC ID initial de chaque processeur logique.

L’extrait de code A-4 en appendice montre un exemple de l’interrogation de l’identifiant APIC de chaque processeur logique du système et de leur résolution (parsing) en sous-identifiants pour analyse ultérieure de la constitution topologique.

L’extrait de code A-5 en appendice établit la liste des routines auxiliaires servant à extraire les différents sous-identifiants de chaque APIC ID du processeur logique liaisonné au contexte d’exécution en cours.

L’extrait de code A-6 en appendice montre les fonctions d’encapsulation (wrapper) particulière à l’OS.

Enumération topologique cache

La mémoire cache du paquet physique d’un processeur de technologie Intel 64 est hiérarchisée et un ou plusieurs processeurs logiques peuvent se partager l’un ou l’autre de ces niveaux hiérarchiques. Or certains logiciels sont conçus pour optimiser leurs performances en exploitant l’un de ces niveaux de mémoire cache partagée. L’optimisation des performances à l’aide de la topologie cache peut se réaliser par l’association des informations topologiques cache et système. La figure 4 ci-dessous schématise les procédures de décomposition des sous-identifiants pour l’énumération des processeurs logiques qui partagent le niveau de cache concerné ainsi que les caches du niveau correspondant visibles dans le système. Pour les processeurs qui disposent d’un identifiant x2APIC 32 bits, le Cache_ID peut s’extraire à partir de celui-ci et, dans le cas contraire, à partir de leur identifiant APIC initial. Le tableau de Cache_ID peut servir à l’optimisation du code, grâce à l’énumération de différents caches conjointement à d’autres sous-identifiants obtenus à partir de la topologie processeur.

Figure 4. Procédures d’extraction du Cache_ID du niveau concerné pour chaque processeur logique.

Les logiciels peuvent exploiter les trois niveaux de sous-identifiants (SMT_ID[k], Core_ID[k], Pkg_ID[k], k = 0, …, N-1) de diverses manières et différemment les uns des autres. Voici certains de leurs usages parmi les plus courants :

  1. Détermination du nombre de processeurs physiques pour mettre en place des règles de respect des licences logicielles par paquet, chaque valeur individuelle du tableau Pkg_ID[] représentant un processeur physique.
  2. Dans le cadre d’une stratégie de liaisonnement des threads (thread binding), on peut liaisonner chaque nouvelle tâche à l’un des cœurs du système, ce qui peut supposer que le logiciel connaisse la relation entre le masque d’affinité de chaque processeur logique par rapport à chacun des cœurs de traitement.
  3. Dans le cadre d’une stratégie d’optimisation par dimensionnement multiprocesseur, on peut partitionner l’espace de travail des données (working set) en fonction de la capacité du cache de dernier niveau et laisser plusieurs threads traiter la tuile de données (tile) de chaque cache de dernier niveau. Cette opération nécessite que le logiciel gère les masques d’affinité et le liaisonnement des threads en fonction de chaque Cache_ID et APIC_ID du système.

Traitement des sous-identifiants de la topologie

Chaque niveau hiérarchique de sous-identifiants représente un sous-ensemble de l’identifiant APIC (x2APIC ou initial). Il permet aux logiciels d’adresser chaque entité distincte du niveau hiérarchique directement supérieur. Pour l’énumération topologique processeur :

  • SMT ID. Chaque SMT_ID distinct permet aux logiciels de distinguer les différents processeurs logiques d’un cœur d’exécution.
  • Core ID. Chaque Core_ID distinct permet aux logiciels de distinguer les différents cœurs d’un paquet physique.
  • Pkg ID. Chaque Pkg_ID distinct permet aux logiciels de distinguer les différents paquets physiques d’une configuration multiprocesseur.

Pour l’énumération topologique cache :

  • CacheSMT ID. Chaque CacheSMT_ID distinct permet aux logiciels de distinguer les différents processeurs logiques qui partagent le même niveau de cache cible.
  • Cache ID. Chaque Cache_ID distinct permet aux logiciels de distinguer le niveau de cache cible de la configuration.

L’extraction des sous-identifiants APIC exploite les paramètres constants qui résultent de l’instruction CPUID. Du point de vue de la plate-forme matérielle, les configurations multiprocesseurs de technologie Intel 64 ou IA-32 imposent que chaque processeur physique prenne en charge les mêmes capacités matérielles de multithreading. L’énumération topologique système peut donc exécuter les fonctions de feuille CPUID voulues, sur un processeur logique, et en tirer l’ensemble des paramètres d’extraction des sous-identifiants du système, mais c’est par l’exécution de l’instruction CPUID sur chaque processeur logique du système que les identifiants APIC doivent être interrogés.

Paramètres d’extraction des sous-identifiants x2APIC

L’extraction des sous-identifiants x2APIC revient à interroger la valeur de CPUID.(EAX=11, ECX=n):EAX[4:0] pour un index de sous-feuille n valide, afin d’obtenir le paramètre de largeur et en tirer un masque d’extraction, tandis que l’identifiant x2APIC s’interroge quant à lui par CPUID.(EAX=1,ECX=0):EDX[31:0]. Le masque d’extraction permet aux logiciels d’extraire un sous-ensemble de bits de l’identifiant x2APIC sous la forme d’un sous-identifiant pour le niveau hiérarchique correspondant. Pour l’énumération des sous-identifiants, on incrémente l’indice de sous-feuille (ECX=n) d’une unité (1) jusqu’à ce que CPUID.(ECX=11,ECX=n).EBX[15:0] soit nul.

  • SMT ID. CPUID.(EAX=11, ECX=0):EAX[4:0] indique le paramètre de largeur permettant d’obtenir une sélection de masques pour extraire les SMT_ID des processeurs logiques d’un même cœur d’exécution. L’index de sous-feuille (ECX=0) se définit architecturalement et s’associe au niveau de type « SMT » (CPUID.(EAX=11, ECX=0):ECX[15:8] = 1).
    • SMT_Mask_Width = CPUID.(EAX=11, ECX=0):EAX[4:0] si CPUID.(EAX=11, ECX=0):ECX[15:8] est égal à 1
    • SMT_Select_Mask = ~((-1) << SMT_Mask_Width )
    • SMT_ID = x2APIC_ID & SMT_Select_Mask
  • Core ID. Le type de niveau correspondant à l’indice de sous-feuille (ECX=1) peut varier entre processeurs disposant de capacités de multithreading matérielles différentes. Si CPUID.(EAX=11, ECX=1):ECX[15:8] est égal à 2, il est associé au niveau « cœur d’exécution ». Ensuite, CPUID.(EAX=11,ECX=1):EAX[4:0] fournit le paramètre de largeur permettant d’obtenir une sélection de masques dans un même paquet physique. Le niveau « cœur d’exécution » inclut dans ce cas le niveau « SMT » et l’énumération des cœurs d’un paquet peut d’effectuer en mettant à zéro la partie SMT du masque inclusif qui en découle.
    • CorePlus_Mask_Width = CPUID.(EAX=11,ECX=1):EAX[4:0] si CPUID.(EAX=11, ECX=1):ECX[15:8] est égal à 2.
    • CoreOnly_Select_Mask = (~((-1) << CorePlus_Mask_Width ) ) ^ SMT_Select_Mask.
    • Core_ID = (x2APIC_ID & CoreOnly_Select_Mask) >> SMT_Mask_Width
  • Pkg ID. Dans un domaine cohérent à topologie à trois niveaux, les bits de poids fort de l’identifiant APIC (sauf les bits de poids faible CorePlus_Mask_Width ) peuvent énumérer différents paquets physiques d’un système. Dans le cadre d’une installation en grappe, il est possible qu’il faille consulter la documentation du ou des éditeurs concernés pour distinguer la topologie du nombre de paquets physiques hiérarchisés dans tel ou tel nœud.
    • Pkg_Select_Mask = (-1) < ;< CorePlus_Mask_Width
    • Pkg_ID = (x2APIC_ID & Pkg_Select_Mask) >> CorePlus_Mask_Width

On trouvera un exemple de dérivation des paramètres d’extraction de l’identifiant x2APIC en appendice, dans la fonction auxiliaire CPUTopologyLeafBConstants().

L’extrait de code A-7 en appendice indique la fonction auxiliaire permettant d’obtenir les paramètres d’extraction des masques binaires à partir de CPUID Leaf 0BH afin d’extraire les sous-identifiants de l’identifiant x2APIC.

Paramètres d’extraction des sous-identifiants pour l’APIC ID initial

L’extraction topologique des sous-identifiants d’un INITIAL_APIC_ID (CPUID.1:EBX[31:24]) s’effectue à partir des paramètres issus de CPUID.1:EBX[23:16] et CPUID.(EAX=04H, ECX=0):EAX[31:26]. CPUID.1:EBX[23:16] représente le nombre maximal d’identifiants adressables (APIC ID initial) qui peuvent être attribués à des processeurs logiques d’un paquet physique. Cette valeur peut ne pas être égale au nombre de processeurs logiques sur la puce d’un paquet physique. La valeur de (1+(CPUID.(EAX=4, ECX=0):EAX[31:26] )) représente le nombre maximum d’identifiants adressables (Core_ID) qui peuvent servir à l’énumération de cœurs de traitement d’un paquet physique. Là encore, cette valeur peut ne pas être égale au nombre de cœurs d’exécution présents sur la puce d’un paquet physique.

  • SMT ID. La largeur de masque  SMT_Mask_Width équivalente peut s’obtenir en divisant le nombre maximal d’identifiants APIC initiaux adressables par le nombre maximal d’identifiants Core_ID adressables.
    • SMT_Mask_Width = Log2[1](RoundToNearestPof2(CPUID.1:EBX[23:16]) / ((CPUID.(EAX=4, ECX=0):EAX[31:26])+1)), où la fonction Log2 renvoie le logarithme en base 2 et l’opération RoundToNearestPof2() arrondit l’entier d’entrée à la puissance de 2 entière la plus proche et supérieure ou égale à la valeur d’entrée.
    • SMT_Select_Mask = ~((-1) << SMT_Mask_Width )
    • SMT_ID = INITIAL_APIC_ID & SMT_Select_Mask
  • Core ID. La valeur de (1+(CPUID.(EAX=04H, ECX=0):EAX[31:26])) peut également servir à obtenir une largeur de masque CoreOnly_Mask_Width équivalente.
    • CoreOnly_Mask_Width = Log2(1 + (CPUID.(EAX=4, ECX=0):EAX[31:26] ))
    • CoreOnly_Select_Mask = (~((-1) << (CoreOnly_Mask_Width + SMT_Mask_Width) ) ) ^ SMT_Select_Mask.11CPUID11EAX11ECX0CPUIDEBXCPUID
    • Core_ID = (INITIAL_APIC_ID & CoreOnly_Select_Mask) >> SMT_Mask_Width
  • Pkg ID. Le masque Pkg_Select_Mask peut s’obtenir comme suit :
    • CorePlus_Mask_Width = CoreOnly_Mask_Width + SMT_Mask_Width
    • Pkg_Select_Mask = ((-1) << CorePlus_Mask_Width)
    • Pkg_ID = (INITIAL_APIC_ID & Pkg_Select_Mask) >> CorePlus_Mask_Width

L’extrait de code A-8 en appendice indique la fonction auxiliaire permettant d’obtenir les paramètres d’extraction des masques binaires à partir de CPUID Leaf 01H et Leaf 04H afin d’extraire les sous-identifiants de l’APIC ID initial. L’extrait de code A-9 indique la fonction auxiliaire permettant d’obtenir les largeurs de masque à partir de l’ensemble des paramètres d’extraction du système.

Paramètres d’extraction de l’identifiant de cache

Les identifiants de cache sont particuliers à un niveau ciblé de cache de la hiérarchie correspondante. Le logiciel doit déterminer a priori le niveau de cache cible (l’index  n du sous-niveau associé à CPUID Leaf 4) qu’il souhaite optimiser en fonction de la topologie processeur. Après avoir choisi la sous-feuille ECX=n, Log2(RoundToNearestPof2((1 + CPUID.(EAX=4, ECX=n):EAX[25:14])) est alors le paramètre Cache_Mask_Width équivalent. Celui-ci est la base de construction soit d’une sélection de masques pour extraire les sous-identifiants des processeurs logiques qui partagent le niveau de cache cible, soit d’un masque complémentaire pour sélectionner le bit de poids fort de l’identifiant APIC et repérer différentes entités de cache du niveau cible spécifié du système. Pour construire un masque d’extraction des sous-identifiants des processeurs logiques partageant un cache, ce paramètre est simplement ~((-1)<<Cache_Mask_Width ).

L’opération permettant d’obtenir les paramètres d’extraction de masques binaires pour la topologie cache est analogue à celles indiquées dans l’extrait de code A-8 en appendice. Les logiciels peuvent s’axer sur un niveau de cache particulier de la hiérarchie correspondante. On trouvera dans le paquetage téléchargeable en complément de ce document des exemples de code pour obtenir les paramètres d’extraction de masques binaires correspondant à chaque niveau de cache ainsi que des exemples de tri de la topologie cache concernée. (Pour des raisons de longueur, l’appendice ne reprend pas le code topologique cache dans sa totalité.)

Analyse du résultat de l’analyse topologique et individualisation

Comment les logiciels peuvent-ils exploiter les informations topologiques (sous la forme de sous-identifiants hiérarchiques) ? Cela dépend entièrement des besoins et des conditions particulières à chaque logiciel. Ces informations peuvent nécessiter une adaptation en raison des différences entre les API fournies par tel ou tel OS. A titre d’illustration, nous allons envisager certains exemples d’utilisation des sous-identifiants pour établir et gérer hiérarchiquement des masques d’affinité.

Connaître les sous-identifiants de chaque hiérarchie topologique peut être utile à divers titres ; par exemple :

  1. Pour dénombrer les entités d’un niveau hiérarchique donné sur l’ensemble du système.
  2. Pour utiliser les services de gestion des threads de l’OS (masques d’affinité, par exemple) tout en enrichissant les renseignements topologiques (par cœur, par paquet, par niveau de cache cible) afin d’optimiser les performances applicatives.

Un masque d’affinité est une structure de données définie dans un OS particulier. Des OS différents sont susceptibles d’exploiter ce concept, mais en proposant des moyens d’interface de programmation applicative distincts. Ainsi, Microsoft Windows* propose un masque d’affinité sous forme d’un type de données directement manipulable via champ de bits par les applications pour le contrôle d’affinité. Linux*, quant à lui, met en œuvre en interne une structure de données similaire, mais l’abstrait pour que les applications puissent manipuler l’affinité par le biais d’une interface interactive qui attribue à chaque processeur logique des énumérateurs partant de zéro.

Le masque d’affinité ou le système de numérotation équivalent fourni par l’OS ne porte pas d’attributs capables de stocker ceux de la topologie système. Nous allons donc ici utiliser la locution masque d’affinité de manière générique (car la technique peut facilement se généraliser à l’interface numérotée du contrôle d’affinité).

Dans l’exemple de code en référence, nous allons utiliser les sous-identifiants pour créer un système de numération ordinale (partant de zéro) pour chaque niveau hiérarchique. Différentes entités de la topologie système (paquets, cœurs) peuvent être référencées par les applications qui emploient un ensemble de nombres ordinaux hiérarchiques. A l’aide de ce système et d’une table de conversion avec les masques d’affinité correspondants, les logiciels peuvent facilement piloter le liaisonnement des threads, optimiser le taux d’utilisation du cache, etc.

La figure 4 ci-dessous donne un exemple de base du traitement des identifiants Pkg_ID et Core_ID dans le système pour l’acquisition d’informations sur le nombre de paquets physiques et de cœurs d’exécution qui leur sont visibles. Cette technique de base peut également s’adapter à l’acquisition de mappages d’affinité, de décompositions hiérarchiques et d’informations d’asymétrie sur le système.

Les extraits de code A-10a, b et c en appendice exposent un algorithme d’analyse des sous-identifiants de tous les processeurs logiques du système et renvoient un triplet d’un système de numérotation en partant de zéro pour indexer de façon univoque les entités à chaque niveau topologique.

L’extrait de code A-11 reprend quant à lui une structure de données qui hiérarchise diverses variables globales, tableaux et éléments de l’espace de travail utilisés tout au long du reste du code. On trouvera l’intégralité de ce code source dans un paquetage distinct. Ce code peut se compiler sous Windows ou Linux 32 ou 64 bits. Il n’a été testé qu’avec quelques systèmes d’exploitation et compilateurs.

Visibilité dynamique par les logiciels de l’énumération topologique

Lorsqu’un logiciel examine ou utilise des informations topologiques, il doit prendre en compte la nature dynamique de la visibilité qu’il en a. Les capacités matérielles présentes au niveau de la plate-forme peuvent se présenter différemment selon qu’elles le sont par le paramétrage du BIOS, par l’option d’amorçage de l’OS ou par les interfaces utilisateur gérées par l’OS. Ainsi, les systèmes multiprocesseurs dotés de puces de technologie Intel 64 ou IA-32 imposent à chaque processeur physique de prendre en charge les mêmes capacités de multithreading matériel. Cette symétrie matérielle qui existe au niveau de la plate-forme matérielle peut se présenter différemment au niveau applicatif. L’énumération topologique système peut révéler une asymétrie dynamique visible aux logiciels qu’elle qu’en soit la cause (paramétrage du BIOS, option d’amorçage de l’OS ou configurations des interfaces utilisateur).

L’appendice à ce document établit la liste de la majorité des fonctions auxiliaires utilisées pour l’énumération de la topologie processeur d’un système et visibles par le processus logiciel en cours. Un code source complet est par ailleurs disponible au téléchargement. Le code de référence peut être compilé en environnement Windows 32 ou 64 bits. En environnement 64 bits, le fichier cpuid64.asm fournit la fonction intrinsèque indispensable pour interroger les sous-feuilles CPUID. Un code de référence équivalent pour les environnements Linux 32 et 64 bits sera proposé ultérieurement.

Glossaire

Cœur d’exécution. Circuit qui assure des fonctions dédiées de décodage et d’exécution des instructions ainsi que de transmission des donnés entre certains sous-systèmes d’un paquet physique. Un cœur d’exécution peut regrouper un ou plusieurs processeurs logiques.

Hiérarchie de cache. Disposition physique des niveaux de cache qui tamponnent le transport des données entre une entité de processeur et le sous-système mémoire physique.

Multithreading matériel. L’un des trois ensembles de ressources matérielles permettant à un système de gérer des logiciels multithreads : le multithreading simultané (SMT), le multicœur et le multiprocesseur.

Multithreading simultané. En anglais : Simultaneous Multi-Threading (SMT). Procédé économe (en temps d’exécution et de gestion des changements de contexte des threads), mis en œuvre au niveau de la puce, pour faire assurer à un cœur d’exécution les fonctions de plusieurs processeurs logiques, grâce au partage hiérarchisé des ressources d’exécution et du cache entre ces derniers.

Plate-forme multiprocesseur. Ordinateur équipé de deux ou plusieurs interfaces matérielles d’échange de données (sockets physiques), chacune avec un ou plusieurs processeurs physiques associés.

Processeur logique. Modularité fondamentale des moyens matériels du processeur qui permet aux exécutifs logiciels (OS) de dispatcher les tâches ou d’exécuter un contexte de thread. Chaque processeur logique ne peut exécuter qu’un seul contexte de thread à la fois.

Processeur multicœur. Processeur physique qui intègre plusieurs cœurs de traitement.

Processeur physique. Paquet physique d’un microprocesseur capable d’exécuter un ou plusieurs threads logiciels en parallèle. Chaque paquet physique s’installe sur une interface matérielle d’échange de données (socket physique) et peut comporter un ou plusieurs cœurs d’exécution, pour lesquels on utilise aussi globalement le terme paquet physique.

Technologie Hyper-Threading. Caractéristique de la famille des processeurs IA-32, en vertu de laquelle chaque cœur d’exécution d’une puce assure les fonctions de deux processeurs logiques ou davantage.

Topologie cache. Relations hiérarchiques d’un niveau de cache par rapport aux processeurs logiques d’un processeur physique.

Topologie processeur. Relations hiérarchiques entre les entités (processeurs logiques, cœurs d’exécution) d’un processeur physique en fonction de la hiérarchie de partage des ressources matérielles de ce dernier.

Appendice

Extrait de code A-1. Détermination des constantes topologiques processeur à l’échelle d’un système.

// Derive parameters used to extract/decompose APIC ID for CPU topology

// The algorithm assumes CPUID feature symmetry across all physical packages.

// Since CPUID reporting by each logical processor in a physical package are

// identical, we only execute CPUID on one logical processor to derive these

// system-wide parameters

// return 0 if successful, non-zero if error occurred

static int CPUTopologyParams()

{

DWORD maxCPUID; // highest CPUID leaf index this processor supports

CPUIDinfo info; // data structure to store register data reported by CPUID

_CPUID(&info, 0, 0);

maxCPUID = info.EAX;

// cpuid leaf B detection

if (maxCPUID >= 0xB)

{

CPUIDinfo CPUInfoB;

_CPUID(&CPUInfoB,0xB, 0);

//glbl_ptr points t
o assortment of global data, workspace, etc

glbl_ptr->hasLeafB = (CPUInfoB.EBX != 0);

}

_CPUID(&info, 1, 0);

// Use HWMT feature flag CPUID.01:EDX[28] to treat three configurations:

if (getBitsFromDWORD(info.EDX,28,28))

{ // #1, Processors that support CPUID leaf 0BH

if (glbl_ptr->hasLeafB)

{ // use CPUID leaf B to derive extraction parameters

CPUTopologyLeafBConstants();

}

else //#2, Processors that support legacy parameters

// using CPUID leaf 1 and leaf 4

{

CPUTopologyLegacyConstants(&info, maxCPUID);

}

}

else //#3, Prior to HT, there is only one logical

//processor in a physical package

{

&
nbsp; glbl_ptr->CoreSelectMask = 0;

glbl_ptr->SMTMaskWidth = 0;

glbl_ptr->PkgSelectMask = (-1);

glbl_ptr->PkgSelectMaskShift = 0;

glbl_ptr->SMTSelectMask = 0;

}

if( glbl_ptr->error)return -1;

else return 0;

}

Extrait de code A-2. Structure modulaire pour l’obtention des informations d’énumération topologique système.

/*

* BuildSystemTopologyTables

*

* Construct the processor topology tables and values necessary to

* support the external functions that display CPU topology and/or

* cache topology derived from system topology enumeration.

* Arguments: None

* Return: None, sets glbl_ptr->error if tables or values can not be calculated.

*/

static void BuildSystemTopologyTables()

{ unsigned lcl_OSProcessorCount, subleaf;

int numMappings = 0;

// call OS-specific service to find out how many logical processors

// are supported by the OS

glbl_ptr->OSProcessorCount = lcl_OSProcessorCount = GetMaxCPUSupportedByOS();

// allocated the memory buffers within the global pointer

AllocArrays(lcl_OSProcessorCount);

// Gather all the system-wide constant parameters needed to

// derive topology information

if (CPUTopologyParams() ) return ;

if (CacheTopologyParams() ) return ;

// For each logical processor, collect APIC ID and

// parse sub IDs for each APIC ID

numMappings = QueryParseSubIDs();

if ( numMappings < 0 ) return ;

// Derived separate numbering schemes for each level of the cpu topology

if( AnalyzeCPUHierarchy(numMappings) < 0 ) {

glbl_ptr->error |= _MSGTYP_TOPOLOGY_NOTANALYZED;

}

// an example of building cache topology info for each cache level

if(glbl_ptr->maxCacheSubleaf != -1) {

for(subleaf=0; subleaf <= glbl_ptr->maxCacheSubleaf; subleaf++) {

if( glbl_ptr->EachCacheMaskWidth[subleaf] != 0xffffffff) {

// ensure there is at least one core in the target level cache

if (AnalyzeEachCHierarchy(subleaf, numMappings) < 0) {

glbl_ptr->error |= _MSGTYP_TOPOLOGY_NOTANALYZED;

&nb
sp; }

}

}

}

}

Extrait de code A-3. Structures de l’identifiant APIC et de ses sous-identifiants ;
mappage de systèmes de numérotation ordinale.

typedef struct {

unsigned int32 APICID; // the full x2APIC ID or initial APIC ID of a logical

// processor assigned by HW

unsigned __int32 OrdIndexOAMsk; // An ordinal index (zero-based) for each logical

// processor in the system, 1:1 with "APICID"

// Next three members are the sub IDs for processor topology enumeration

unsigned __int32 pkg_IDAPIC; // Pkg_ID field, subset of APICID bits

// to distinguish different packages

unsigned __int32 Core_IDAPIC; // Core_ID field, subset of APICID bits to

// distinguish different cores in a package

unsigned __int32 SMT_IDAPIC; &n
bsp; // SMT_ID field, subset of APICID bits to

// distinguish different logical processors in a core

// the next three members stores a numbering scheme of ordinal index

// for each level of the processor topology.

unsigned __int32 packageORD; // a zero-based numbering scheme for each physical

// package in the system

unsigned __int32 coreORD; // a zero-based numbering scheme for each core in the

// same package

unsigned __int32 threadORD; // a zero-based numbering scheme for each thread in

// the same core

// Next two members are the sub IDs for cache topology enumeration

unsigned __int32 EaCacheSMTIDAPIC[MAX_CACHE_SUBLEAFS]; // SMT_ID field, subset of

// APICID bits to distinguish different logical processors

// sharing the same cache level

unsigned __int32 EaCacheIDAPIC[MAX_CACHE_SUBLEAFS]; // sub ID to enumerate

// different cache entities of the cache level corresponding

// to the array index/cpuid leaf 4 subleaf index

// the next three members stores a numbering scheme of ordinal index

// for enumerating different cache entities of a cache level, and enumerating

// logical processors sharing the same cache entity.

unsigned __int32 EachCacheORD[MAX_CACHE_SUBLEAFS]; // a zero-based numbering

// scheme for each cache entity of the specified cache level in the system

unsigned __int32 threadPerEaCacheORD[MAX_CACHE_SUBLEAFS]; // a zero-based

// numbering scheme for each logical processor sharing the same cache of the

// specified cache level

} IdAffMskOrdMapping;

/* Alternate technique for ring 3 code to infer the effect of CMOS setting in BIOS

* that restricted CPUID instruction to report highest leaf index
is 2, i.e.

* MSR IA32_MISC_ENABLES[22] was set to 1; This situation

* will prevent software from using CPUID to conduct topology enumeration

* RDMSR instruction is privileged, this alternate routine can run in ring 3.

*/

Int InferBIOSCPUIDLimitSetting()

{ DWORD maxleaf, max8xleaf;

CPUIDinfo info; // data structure to store register data reported by CPUID

// check CPUID leaf reporting capability is intact

CPUID(&info, 0);

maxleaf = info.EAX;

CPUID(&info, 0x80000000);

max8xleaf = info.EAX;

// Earlier Pentium 4 and Intel Xeon processor (prior to 90nm Intel Pentium 4

// processor)support extended with max extended leaf index 0x80000004,

// 90nm Intel Pentium 4 processor and later processors supports higher extended

// leaf index greater than 0x80000004.

If ( maxleaf <= 4 && max8xleaf > 0x80000004) return 1;

else return 0;

}

Extrait de code A-4. Identifiant APIC et sa résolution en sous-identifiants.

/*

* QueryParseSubIDs

*

* Use OS specific service to find out how many logical processors can be accessed

* by this application.

* Querying CPUID on each logical processor requires using OS-specific API to

* bind current context to each logical processor first.

* After gathering the APIC ID's for each logical processor,

* we can parse APIC ID into sub IDs for each topological levels

* The thread affnity API to bind the current context limits us

* in dealing with the limit of specific OS

* The loop to iterate each logical processor managed by the OS can be done

* in a manner that abstract the OS-specific affinity mask data structure.

* Here, we construct a generic affinity mask that can handle arbitrary number

* of logical processors.

* Return: 0 is no error

*/

long QueryParseSubIDs(void)

{ unsigned i;

//DWORD_PTR processAffinity;

//DWORD_PTR systemAffinity;

unsigned long numMappings = 0, lcl_OSProcessorCount;

unsigned long APICID;

// we already queried OS how many logical processor it sees.

lcl_OSProcessorCount = glbl_ptr->OSProcessorCount;

// we will use our generic affinity bitmap that can be generalized from

// OS specific affinity mask constructs or the bitmap representation of an OS

AllocateGenericAffinityMask(&glbl_ptr->cpuid_values_processAffinity, lcl_OSProcessorCount);

AllocateGenericAffinityMask(&glbl_ptr->cpuid_values_systemAffinity, lcl_OSProcessorCount);

// Set the affinity bits of our generic affinity bitmap according to

// the system affinity mask and process affinity mask

SetChkProcessAffinityConsistency(lcl_OSProcessorCount);

if (glbl_ptr->error) return -1;

for (i=0; i < glbl_ptr->OSProcessorCount;i++) {

// can't asume OS affinity bit mask is contiguous,

// but we are using our generic bitmap representation for affinity

if(TestGenericAffinityBit(&glbl_ptr->cpuid_values_processAffinity, i) == 1) {

// bind the execution context to the ith logical processor

// using OS-specifi API

&n
bsp; if( BindContext(i, glbl_ptr->cpuid_values_OSProcessorCount) ) {

glbl_ptr->error |= _MSGTYP_UNKNOWNERR_OS;

break;

}

// now the execution context is on the i'th cpu, call the parsing routine

ParseIDS4EachThread(i, numMappings);

numMappings++;

}

}

glbl_ptr->EnumeratedThreadCount = numMappings;

if( glbl_ptr->error)return -1;

else return numMappings;

};

Tableau A-5. Routine auxiliaire pour la résolution de l’identifiant APIC en sous-identifiants.

/*

* ParseIDS4EachThread

* after execution context has already bound to the target logical processor

* Query the 32-bit x2APIC ID if the processor supports it, or

* Query the 8-bit initial APIC ID for older processors. Apply various

* system-wide topology constant to parse the APIC ID into various sub IDs

* Arguments:

* i : the ordinal index to reference a logical processo
r in the system

* numMappings : running count ot how many processors we've parsed

* Return: 0 is no error

*/

unsigned ParseIDS4EachThread(unsigned i, unsigned numMappings)

{ unsigned APICID;

unsigned subleaf;

APICID = glbl_ptr->PApicAffOrdMapping[numMappings].APICID = GetApicID(i);

glbl_ptr->PApicAffOrdMapping[numMappings].OrdIndexOAMsk = i;

// this an ordinal number that can relate to generic affinitymask

glbl_ptr->PApicAffOrdMapping[numMappings].pkg_IDAPIC = ((APICID & glbl_ptr->PkgSelectMask)

>> glbl_ptr->PkgSelectMaskShift);

glbl_ptr->PApicAffOrdMapping[numMappings].Core_IDAPIC = ((APICID & glbl_ptr->CoreSelectMask)

>> glbl_ptr->SMTMaskWidth);

glbl_ptr->PApicAffOrdMapping[numMappings].SMT_IDAPIC = (APICID & glbl_ptr->SMTSelectMask);

if(glbl_ptr->maxCacheSubleaf != -1) {

for(subleaf=0; subleaf <= glbl_ptr->maxCacheSubleaf; subleaf++) {

glbl_ptr->PApicAffOrdMapping[numMappings].EaCacheSMTIDAPIC[subleaf]

= (APICID & glbl_ptr->EachCacheSelectMask[subleaf]);

&nbs