• Aucun résultat trouvé

Il va sans dire que la portabilité de OpenCL est un avantage indéniable pour la programma- tion visant des plateformes hétérogènes comportant plusieurs types de processeurs. En effet, écrire un code spécifique dans un langage propre à chaque architecture est un long processus qui peut s’avérer contre-productif. OpenCL permet au même code d’être exécuté sur un ou une combinaison de processeurs d’architectures différentes contenues dans un même ordinateur. Cette portabilité est la principale force du standard. Contrairement à MPI, OpenCL ne permet pas de synchroniser les calculs entre plusieurs ordinateurs. Toutefois, MPI et OpenCL peuvent être utilisés conjointement pour créer un environnement unifiant tous les engins de calculs hétérogènes d’un réseau d’ordina- teurs. Bref, pour profiter des différents niveaux de parallélisme d’une grappe de calcul hétérogène, le code sismique qui sera décrit aux sections suivantes de ce chapitre utilise une approche hybride OpenCL/MPI. Avant tout, quelques notions essentielles sur OpenCL sont discutées dans cette sec- tion. Le lecteur est référé à Kaeli et al. (2015) pour une revue exhaustive du sujet.

OpenCL repose sur une abstraction de l’architecture d’une plateforme de calcul hétérogène com- portant plusieurs processeurs parallèles. Ce modèle général est présenté à la Figure 4.4. Au niveau hiérarchique le plus élevé, il y a l’hôte, c’est-à-dire le programme géré par le système d’exploitation sur le CPU. L’hôte permet de gérer les différents engins de calculs accessibles localement; il envoie les instructions aux différents engins de calcul et gère la communication des données entre ceux-ci. En quelque sorte, l’hôte est une unité de contrôle virtuelle de l’architecture de von Neumann et les engins de calculs sont des unités arithmétiques virtuelles. Un engin de calcul peut concrètement consister en un CPU, un GPU, un FPGA, etc. Chaque engin de calcul peut comprendre plusieurs unités de calcul, c’est-à-dire des coeurs pour des GPUs ou des CPUs. Enfin chaque coeur peut com- porter plusieurs éléments de traitements ou fils d’exécution. Ainsi, tout comme dans le modèle d’une grappe de calcul présenté à la section précédente, OpenCL comporte trois niveaux de parallélisme: le niveau des engins de calculs, le niveau de l’unité de calcul et le niveau de l’élément de traitement. De même, plusieurs niveaux de mémoire existent: la mémoire de l’hôte, qui consiste en la mémoire accessible au système d’exploitation. À l’intérieur d’un engin de calcul, il y a une mémoire globale, accessible par toutes les unités de calculs. Chaque unité de calcul comprend un mémoire locale, partagée par les éléments de traitement. Finalement, chaque élément de traitement possède sa propre mémoire, appelée mémoire privée. Ces niveaux de mémoires ont des performances et des capacités qui varient selon une architecture de processeur à l’autre. En général, le volume de mémoire décroit de la mémoire globale à la mémoire privée et croit en termes de rapidité d’accès.

Ce modèle abstrait schématise la plupart des architectures de processeurs existants. Cependant, l’optimisation d’un code OpenCL varie grandement d’un algorithme à l’autre et doit prendre en compte les particularités d’une architecture donnée. Dans ce travail, une attention particulière a été portée à l’optimisation des performances sur les GPU, ce type de processeur étant le plus efficace pour les calculs par différences finies. Michéa & Komatitsch (2010) décrivent en détail les concepts de programmation GPU appliqués à la modélisation sismique par différences finies. En particulier, les éléments de traitement dans un GPU Nvdia sont regroupés en groupe de 32, ce qui est dénommé par un warp en langage Nvidia, ou un groupe de travail en langage OpenCL. À l’intérieur d’un groupe de travail, les éléments de traitement peuvent être synchronisés et partagent la mémoire locale. Chaque unité de traitement doit effectuer les mêmes instructions, sur différentes adresses de mémoires. Une attention particulière doit être portée à l’accès de la mémoire à l’intérieur d’un

patron d’accès. Ainsi, lorsque chacun des éléments d’un warp accède à une adresse de mémoire adjacente, l’écriture ou la lecture se fait simultanément pour tous les éléments de traitement. Dans le cas contraire, plusieurs accès à la mémoire sont requis. Comme l’accès à la mémoire globale prend plusieurs cycles, la réduction du nombre d’accès est primordiale pour obtenir de bonne performance avec les GPUs. De plus, en différences finies, le nombre d’instructions par cycles d’écritures/lectures de mémoire est très bas, augmentant l’importance de limiter l’accès à ce niveau de mémoire. De même, l’utilisation de la mémoire locale, qui est beaucoup plus rapide que la mémoire globale, permet de diminuer le nombre de lectures et d’écritures. Bref, le patron d’accès à la mémoire est un des aspects principaux qui doit être optimisé sur GPU avec OpenCL.

En pratique, un programme basé sur OpenCL se déroule comme suit. Premièrement, des com- mandes sont envoyées à l’hôte afin de détecter les engins de calculs accessibles et de les connecter dans un contexte de calcul unique. Une fois ce contexte établi, des variables de mémoires sont créées afin de pouvoir transférer des éléments de mémoire entre l’hôte et les différents engins de calcul. Ensuite, les kernels sont chargés en mémoire et compilés en temps réel pour chaque engin de calcul. Les kernels sont des routines écrites en C qui comportent les instructions devant être effectuées par les engins de calcul. Par après, la partie principale du programme est exécutée, dans laquelle une série d’appels au kernels et de transferts de mémoire ont lieu. Ces appels sont gérés par l’hôte à l’aide d’un système de queue de commande. Finalement, les variables de mémoires sont libérées avant la fin du programme.

Documents relatifs