Overblog
Editer l'article Suivre ce blog Administration + Créer mon blog
15 mars 2012 4 15 /03 /mars /2012 17:32

 

Lors des deux précédents articles (disponibles ici et ), j'ai évoqué à plusieurs reprises la notion de NUMA sans trop la détailler. NUMA (NonUniform Memory Access) est devenue l'une des architectures matérielles la plus utilisée dans la conception des serveurs. Je profite donc de cet article pour revenir sur son intégration dans Solaris (notamment les versions 10 et 11).

 

En théorie, une machine NUMA est composée d'une ou plusieurs nodes disposant chacune d'un certain nombre de CPU(s) et d'une certaine quantité de mémoire. Toutes les nodes sont interconnectées entre elles et partagent l'intégralité mémoire de la machine. Les temps d'accès mémoire contenue dans une node sont inférieurs aux temps d'accès mémoire contenue dans une node distante (modèle de latence).

 

Un modèle de latence consiste le plus souvent à un ou plusieurs locality groups (lgrps). Chaque lgrp est constitué :

  • d'une ou plusieurs CPU(s)
  • d'une ou plusieurs pages de mémoire physiques

lgroup

 

A noter qu'il existe plusieurs modèles de latence plus ou moins complexes (différents niveaux de latence).

 

Solaris est "aware" de l'architecture matérielle NUMA : les différentes nodes sont des éléments connus du système Solaris. L'association mémoire / CPUs est connue de Solaris sous la forme de groupe de localité (locality group).

 

Le framework MPO (Memory Placement Optimization) est un composant essentiel à l'architecture NUMA. Pour une application en cours de fonctionnement, le système Solaris tente de l'exécuter sur une CPU la plus proche de la mémoire pour minimiser le plus possible les temps d'accès à celle-ci.

 

Quelques notions importantes à connaître :

  • Pour chaque nouveau thread un locality group est sélectionné (home lgrp)
  • L'algorithme d'ordonnancement tentera d'exécuter un thread sur une des CPUs de son home lgrp
  • La classe d'ordonnancement RT n'est pas prise en compte dans cet algorithme

Un thread peut changer de home lgrp uniquement si :

  • Le locality group est détruit
  • Le thread est binder sur une autre CPU d'un autre locality group

Plusieurs paramètres sont disponibles pour modifier le comportement des lgrps. Voir la section suivante : Locality Group Parameters dans Oracle Solaris Tunable Parameters Reference Manuel.

 

 

Maintenant, passons un peu à la pratique... Pour connaître le nombre de lgrps disponible sur un système, il existe deux méthodes : en utilisant la commande kstat ou en passant par mdb :

 

# kstat -m lgrp  
module: lgrp             instance: 1    
name:   lgrp1            class:    misc
    alloc fail           294068397
    cpus                 8
    crtime               343.726918435
    default policy       0
    load average         11296
    lwp migrations       1309
    next-seg policy      0
[...]


# mdb -k

Loading modules: [...]

> ::walk cpu |::print cpu_t cpu_lpl |::print lgrp_t lgrp_id !sort -u
lgrp_id = 0x6bdd
lgrp_id = 0x8356
lgrp_id = 0x840b
lgrp_id = 0xa232

 

La macro lgrp disponible dans mdb permet d'obtenir bien plus d'informations sur les lgrps dont notamment le nombre de CPUs et leurs répartitions. Attention cette macro n'est pas disponible dans toutes les updates de Solaris 10 :

 

# mdb -k
Loading modules: [...]
> ::lgrp
 LGRPID             ADDR           PARENT    PLATHAND   #CPU    CPUS
      0 fffffffffbc20580                0     DEFAULT      0     
      1 fffffffffbc0d440 fffffffffbc20580           0      8    0-7
      2 fffffffffbc0d4a8 fffffffffbc20580           1      8    8-15
      3 fffffffffbc0d510 fffffffffbc20580           2      8    16-23
      4 fffffffffbc0d578 fffffffffbc20580           3      8    24-31

 

Les statistiques des lgrps sont disponibles facilement via la commande kstat. Par exemple pour le lgrp 3, il  suffit de saisir la commande suivante :

 

$ kstat -m lgrp -i 3
module: lgrp                       instance: 3    
name:   lgrp3                      class:    misc
    alloc fail                     557701
    cpus                           8
    crtime                         345.278084135
    default policy                 0
    load average                   48125
    lwp migrations                 41345
    next-seg policy                0
    next-touch policy              9959223943
    pages avail                    33554432
    pages failed to mark           0
    pages failed to migrate from   0
    pages failed to migrate to     0
    pages free                     6976717
    pages installed                33554432
    pages marked for migration     0
    pages migrated from            0
    pages migrated to              0
    random policy                  5496062
    round robin policy             0
    snaptime                       8170819.92639302
    span process policy            0
    span psrset policy             0

 

Pour obtenir le home lgrp d'un thread en cours d'exécution, il suffit d'utiliser mdb (l'adresse du lgrp est stockée dans la structure kthread_t) :

 

# mdb -k

Loading modules: [...]

> 0t26116::pid2proc |::walk thread |::print kthread_t t_lpl |::print struct lgrp_ld lpl_lgrpid

lpl_lgrpid = 0x4

 

Lors de la création d'un thread, il suffit d'utiliser le one-liner Dtrace ci-dessous pour connaître rapidement son home lgrp :

 

# dtrace -qn 'thread_create:return { printf("Created thread (PID %d LWP %d) with home lgroup %d\n", pid, tid, curthread->t_lpl->lpl_lgrpid); }"
Created thread (PID 26539 LWP 1) with home lgroup 3
Created thread (PID 26743 LWP 1) with home lgroup 2
Created thread (PID 26745 LWP 1) with home lgroup 4
Created thread (PID 5913 LWP 1) with home lgroup 3
Created thread (PID 26745 LWP 1) with home lgroup 4
Created thread (PID 18609 LWP 26478) with home lgroup 3
Created thread (PID 18609 LWP 26478) with home lgroup 3
Created thread (PID 26757 LWP 1) with home lgroup 4
Created thread (PID 2473 LWP 7) with home lgroup 4
Created thread (PID 26754 LWP 1) with home lgroup 4
^C
Created thread (PID 26763 LWP 1) with home lgroup 1
Created thread (PID 26771 LWP 1) with home lgroup 2

 

En théorie, un thread sera le plus souvent exécuté sur les CPUs disponibles dans son home lgrp. Pour vérifier cela, j'utilise un petit script Dtrace (inspiré du script getlgrp.d disponible dans Solaris Internals - attention j'ai du modifié quelque peu le code pour qu'il fonctionne sur les dernières versions de Solaris 10) :

 

# mdb -k

Loading modules: [...]

> 0t11517::pid2proc |::walk thread |::print kthread_t t_lpl |::print struct lgrp_ld lpl_lgrpid
lpl_lgrpid = 0x4

 

# ./lgrp.d 11517
^C
Threads CPUs and lgrps for PID 11517

TID   LGRP     CPUID    COUNT  
================================
1     2        8        2      
1     1        2        4      
1     1        7        5      
1     1        3        6      
1     1        5        6      
1     1        6        6      
1     2        9        6      
1     2        10       6      
1     2        13       6      
1     1        0        7      
1     2        11       7      
1     1        4        8      
1     2        12       8      
1     2        15       8      
1     1        1        9      
1     3        16       9      
1     3        20       9      
1     3        22       9      
1     2        14       10     
1     3        21       10     
1     3        17       11     
1     3        23       11     
1     3        19       12     
1     3        18       13     
1     4        26       216    
1     4        31       226    
1     4        28       246    
1     4        24       247    
1     4        29       298    
1     4        25       308    
1     4        27       342    
1     4        30       370 

 

Dans cet exemple, le processus 11517 contient un seul lwp. On constate que celui-ci est exécuté majoritairement sur les différentes CPUs du lgrp 4. L'algorithme du MPO effectue correctement son travail pour favoriser l'exécution du processus sur son home lgrp.

 

Lors de la création d'un fils (processus ou thread), le système Solaris tente de sélectionner une CPU dans le home lgrp du père. Cependant si les ressources souhaitées (notamment mémoire) ne sont pas disponibles, le système choisit alors un nouveau home lgrp pour ce fils.

 

Pour observer ces évènements, j'utilise la fonction lgrp_move_thread() :

 

# ./lgrpmove.d

Thread 1 (pid 13222) from Home lgrp 4 rehomed to New lgrp 3
Thread 1 (pid 18776) from Home lgrp 1 rehomed to New lgrp 2
Thread 1 (pid 18774) from Home lgrp 1 rehomed to New lgrp 4
Thread 864 (pid 18800) from Home lgrp 4 rehomed to New lgrp 2
Thread 1 (pid 18816) from Home lgrp 3 rehomed to New lgrp 1
Thread 1 (pid 18822) from Home lgrp 2 rehomed to New lgrp 1
Thread 1 (pid 15869) from Home lgrp 4 rehomed to New lgrp 3
Thread 1 (pid 15944) from Home lgrp 2 rehomed to New lgrp 1
Thread 1 (pid 18843) from Home lgrp 2 rehomed to New lgrp 3
Thread 1 (pid 15869) from Home lgrp 4 rehomed to New lgrp 3
Thread 1 (pid 15869) from Home lgrp 4 rehomed to New lgrp 3
Thread 1 (pid 18864) from Home lgrp 4 rehomed to New lgrp 1
Thread 1 (pid 15944) from Home lgrp 2 rehomed to New lgrp 1
Thread 1 (pid 18855) from Home lgrp 3 rehomed to New lgrp 2
Thread 1 (pid 18880) from Home lgrp 2 rehomed to New lgrp 3

^C

Thread 1 (pid 18863) from Home lgrp 1 rehomed to New lgrp 3
Thread 1 (pid 18857) from Home lgrp 4 rehomed to New lgrp 3

 

Dans Solaris 11, nous disposons deux nouvelles commandes lgrpinfo et plgrp pour consulter et changer le home lgrp d'un thread (Je vous encourage à consulter avec attention les pages de manuels de ces deux nouvelles commandes).

 

La commande lgrpinfo affiche toutes les informations sur les lgrps :

 

# lgrpinfo
lgroup 0 (root):
        Children: 1 2
        CPUs: 0-11
        Memory: installed 48G, allocated 2.8G, free 45G
        Lgroup resources: 1 2 (CPU); 1 2 (memory)
        Latency: 70197
lgroup 1 (leaf):
        Children: none, Parent: 0
        CPUs: 0 2 4 6 8 10
        Memory: installed 24G, allocated 915M, free 23G
        Lgroup resources: 1 (CPU); 1 (memory)
        Load: 0.168
        Latency: 48058
lgroup 2 (leaf):
        Children: none, Parent: 0
        CPUs: 1 3 5 7 9 11
        Memory: installed 24G, allocated 1.9G, free 22G
        Lgroup resources: 2 (CPU); 2 (memory)
        Load: 0.0259
        Latency: 48058

 

Pour obtenir le home lgrp d'un thread, il suffit simplement simplement de saisir la commande plgrp :

 

# plgrp $$
     PID/LWPID    HOME
    2947/1        2    

 

L'exécution d'une application bénéficiant du meilleur placement est un axe d'optimisation de plus en plus important. Plusieurs recherches vont dans ce sens : après MPO dans Solaris 9 et 10, Solaris 11 utilise aussi des algorithmes de placements pour les I/O (NUMA I/O). Affaire à suivre...

 

 

Ci-joint quelques références sur ce sujet :

 

Partager cet article

commentaires