Arcane  v3.16.6.0
Documentation utilisateur
Chargement...
Recherche...
Aucune correspondance
Les fenêtres mémoires en mémoire partagée en multi-processus

Introduction

Cette page va décrire comment utiliser la mémoire partagée entre les processus d'un même noeud de calcul à l'aide de fenêtres mémoires.

Utilisation

Cette partie est gérée par la classe Arcane::MachineMemoryWindow.

Cette classe peut utiliser trois implémentations de IMachineMemoryWindowBase, une par type de Arcane::IParallelMng. Il est donc possible d'utiliser cette classe de la même façon, que l'on ait un MpiParallelMng, un SequentialParallelMng, un SharedMemoryParallelMng ou un HybridParallelMng (Choix du gestionnaire d'échange de message).

La création d'un objet de ce type est collectif. Une instance de cette classe va créer une fenêtre mémoire composée de plusieurs segments (un par sous-domaine).

L'accès aux éléments des segments n'est pas collectif. L'accès concurrent à un élément est possible (TODO mais les protections n'ont pas encore été ajoutées).

Lors de la construction de cet objet, chaque sous-domaine va fournir une taille de segment. La taille de la fenêtre va être égale à la somme des tailles de segments.

constexpr Integer nb_elem = 14;
IParallelMng* pm = m_parallel_mng;
Integer my_rank = pm->commRank();
MachineMemoryWindow<Integer> window(pm, nb_elem);

Cette fenêtre sera contigüe en mémoire.

Pour accéder à son segment, il est possible d'utiliser la méthode Arcane::MachineMemoryWindow::segmentView().

{
Span av_my_segment(window.segmentView());
Integer iter = 0;
for (Integer& elem : av_my_segment) {
elem = iter * (my_rank + 1);
iter++;
}
}
window.barrier();

Une fois le segment modifié, on peut faire une barrier pour s'assurer que tout le monde a écrit dans son segment avant de s'en servir.

Pour savoir quels sous-domaines se partagent une fenêtre sur le noeud, il est possible de récupérer un tableau de rang.

ConstArrayView<Int32> machine_ranks(window.machineRanks());
Integer machine_nb_proc = machine_ranks.size();

La position des rangs dans ce tableau correspond à la position de leur segment dans la fenêtre.

Pour la lecture des segments des autres sous-domaines du noeud, on peut utiliser la méthode Arcane::MachineMemoryWindow::segmentConstView().

for (Int32 rank : machine_ranks) {
Span av_segment(window.segmentConstView(rank));
for (Integer i = 0; i < nb_elem; ++i) {
if (av_segment[i] != i * (rank + 1)) {
ARCANE_FATAL("Bad element in memory window -- Expected : {0} -- Found : {1}", (i * (rank + 1)), av_segment[i]);
}
}
}

La taille de la fenêtre ne peut pas être modifiée. En revanche, l'implémentation dans Arcane permet de redimensionner les segments de manière collectif (à la condition que la nouvelle taille de la fenêtre soit inférieure ou égale à la taille d'origine).

Note
Une implémentation avec des fenêtres mémoires ayant des segments non-contigües pourrait être plus performante mais rendrait cette fonctionnalité impossible.
window.barrier();
constexpr Integer nb_elem_div = nb_elem / 2;
window.resizeSegment(nb_elem_div);
Remarques
Les éléments de la fenêtre ne sont pas modifiés pendant le redimensionnement.

La fenêtre étant contigüe, l'accès à toute la fenêtre est possible pour tous les sous-domaines.

if (my_rank == machine_ranks[0]) {
Span av_window(window.windowView());
for (Integer j = 0; j < machine_nb_proc; ++j) {
for (Integer i = 0; i < nb_elem; ++i) {
av_window[i + (j * nb_elem)] = machine_ranks[j];
}
}
}
window.barrier();
{
Span av_window(window.windowConstView());
for (Integer j = 0; j < machine_nb_proc; ++j) {
for (Integer i = 0; i < nb_elem; ++i) {
if (av_window[i + (j * nb_elem)] != machine_ranks[j]) {
ARCANE_FATAL("Bad element in memory window -- Expected : {0} -- Found : {1}", machine_ranks[j], av_window[i + (j * nb_elem)]);
}
}
}
}
window.barrier();

Mémoire partagée entre processus

La mémoire partagée entre processus ne doit pas être vu comme la mémoire partagée en multithread. Ce partage n'est fait que sur une partie de la mémoire, pas sur toute la mémoire.

Prenons cette structure :

struct MaStruct
{
MaStruct()
: array_integer(10)
{}
UniqueArray<Integer> array_integer;
};

On peut l'utiliser comme ceci :

MaStruct ma_struct;
ma_struct.array_integer[0] = 123 * (my_rank+1);

Si on utilise cette structure dans une fenêtre, ça donnerait :

MachineMemoryWindow<MaStruct> win_struct(pm, 1);
Span<MaStruct> my_span = win_struct.segmentView();
new (my_span.data()) MaStruct();
my_span[0].array_integer[0] = 123 * (my_rank+1);

On peut afficher la valeur que l'on a attribuée, ça fonctionne correctement :

debug() << "Elem : " << my_span[0].array_integer[0];

Mais si on veut afficher la valeur d'un autre processus :

window.barrier();
Span<MaStruct> other_span = win_struct.segmentView(machine_ranks[(my_rank + 1) % machine_nb_proc]);
debug() << "Elem : " << other_span[0].array_integer[0];
my_span[0].~MaStruct();

En multi-processus (lancement avec mpirun -n 2 ...), le programme va planter (segfault), alors qu'il ne plantera pas en multithreading (lancement avec -A,S=2).

En multi-processus, les attributs du tableau UniqueArray<Integer> array_integer; de la structure ne sont pas alloués en mémoire partagée (les new ou malloc sont faits sur la mémoire locale), les autres processus n'y ont donc pas accès.

Il est aussi important de noter qu'un même emplacement mémoire en mémoire partagée est adressé différemment entre les processus. Donc, si l'on donne un allocateur en mémoire partagée à l'UniqueArray, les adresses utilisées seront valables uniquement localement.