OpenACC 2.0 : l’optimisation des zones de calcul
By   |  February 12, 2014

Appels de fonctions dans les régions accélérées

La version 1.1 d’OpenACC n’était pas très explicite en matière d’appels de fonctions. Il était possible d’utiliser certains intrinsèques de fonctions mathématiques mais les fonctions utilisateurs ne faisaient pas vraiment partie de la norme. Cette situation n’étant pas satisfaisante, les compilateurs se sont mis à accepter les appels de fonctions utilisateurs en tant qu’extensions du standard. La directive routine d’OpenACC 2.0 se veut être une standardisation de l’utilisation de ces fonctions sur un accélérateur. Le problème semble à première vue simple mais les trois niveaux de parallélisme du modèle d’exécution d’OpenACC introduisent un autre degré de complexité – prévisible celui-ci. Par exemple, une fonction qui contient une boucle distribuée sur des gangs ne devrait jamais être appelée dans une boucle distribuée elle aussi sur les gangs. Il en va de même avec les workers et les vectors.

Cette nouvelle directive routine doit être indiquée pour chaque fonction utilisateur appelée depuis une région accélérée. En C et C++, elle s’insère typiquement avant la définition de la fonction tandis qu’en Fortran elle peut être placée n’importe où dans la définition de la fonction ou de la sous-routine. Elle doit également contenir une des clauses gang, worker, vector ou seq pour définir le contexte dans lequel la fonction est appelée ainsi que le niveau maximum autorisé de distribution des calculs dans le corps de la fonction. Par exemple, une routine contenant une clause worker pourra contenir des boucles distribuées sur des worker, vector et seq mais pas sur des gang. De même, cette routine ne pourra pas être appelée depuis le corps d’une boucle worker ou vector. La syntaxe précise est indiquée au listing 6. Les routines qui ne définissent aucune distribution des calculs peuvent être annotées avec la clause seq, ce qui permet de les appeler depuis n’importe quel contexte.

Les fonctions natives CUDA ou OpenCL ne sont pas non plus oubliées. Pour les appeler, une clause supplémentaire, bind, doit être ajoutée à la directive routine devant la déclaration de la fonction. Cette clause indique le nom de la fonction native correspondante. La clause device type peut aussi être utilisée pour sélectionner la fonction native correspondant à chaque cible matérielle. C’est ce que montre le listing 7 où une version CUDA et OpenCL de notre fonction sum est définie. Notez que le mécanisme permettant d’intégrer le code natif n’est pas spécifié dans la norme et peut donc varier d’un compilateur à l’autre.

Une forme alternative de la clause bind peut aussi être utilisée pour générer une version spécialisée d’une routine sur différents accélérateurs. Dans ce cas, l’argument de la clause bind n’est plus une chaîne de caractères mais le nom de la fonction alternative C, C++ ou Fortran qui doit être compilée au préalable avec votre compilateur OpenACC. Les appels à la fonction originale depuis une région accélérée seront ainsi remplacés par des appels à la fonction alternative. Et comme pour les routines natives, la clause device type associée à la clause bind peut être utilisée pour sélectionner la version spécialisée. Un exemple de spécialisation de routine est donné au listing 8.

De nouvelles directives d’optimisation

Le comité de travail OpenACC s’est également penché sur des fonctionnalités de haut niveau pour améliorer l’efficacité des codes dans certains contextes. La clause tile de la directive loop en est un exemple. Elle peut être utilisée pour décomposer un nid de boucles en tuiles (tiles) de taille fixe. Cette transformation classique améliore la localité des accès mémoire au sein d’une tuile et rend ainsi les mémoires caches plus efficaces. Le listing 9 montre un cas typique d’utilisation de la clause tile sur une convolution bidimensionnelle. Le calcul de convolution sur chaque point accède ainsi plusieurs des points voisins dans les deux directions.

Le résultat de l’application de cette clause est illustré au listing 10 qui montre un code équivalent à celui du listing 9 sans utilisation de la clause tile. Chacune des boucles affectées est décomposée en deux boucles : l’une, externe, qui itère sur les tuiles ; l’autre, interne, qui itère sur les éléments de la tuile. Les deux boucles externes et internes sont ensuite fusionnées avec une clause collapse avant que soit appliquée la clause gang à la boucle externe et la clause worker à la boucle interne. En pratique, la réutilisation du cache devrait être améliorée entre les workers d’un même gang.

Le code du listing 10 est bien entendu très simplifié. Celui que génère le compilateur doit gérer les boucles dont le nombre d’itérations n’est pas constant et n’est pas non plus un multiple exact de la taille d’une tuile. Typiquement le genre de codage qu’on apprécie de ne pas faire soi-même ! Sachez pour finir que tile peut être appliquée à plus de deux boucles et peut utiliser tous les niveaux de parallélisme gang, worker et vector d’OpenACC, ce qui en fait une alliée de poids dans la quête de performance à moindre coût de développement.

Voilà, grosso modo, pour ce qui concerne les nouveautés d’OpenACC 2.0 en matière d’optimisation des zones de calcul. Depuis la sortie toute récente d’OpenMP 4.0, qui inclut maintenant le support des accélérateurs, la question nous est souvent posée de savoir si les deux standards fusionneront un jour et, si ce n’est pas le cas, lequel survivra. C’est une question difficile car même si l’objectif initial d’OpenACC était d’accélérer le processus de support des accélérateurs dans OpenMP, les deux spécifications sont aujourd’hui bien trop différentes pour être facilement réunies. OpenACC a l’avantage d’être plus mature et plus complet tandis qu’OpenMP est un standard reconnu et supporté par beaucoup plus d’acteurs du monde HPC. Le modèle OpenMP pour la programmation des accélérateurs est aussi plus flexible. Mais ce n’est pas forcément à son avantage car qui dit flexibilité dit aussi coût élevé en termes de performance. C’est d’ailleurs la raison pour laquelle le modèle CUDA, avec toutes les contraintes qu’il suppose, a connu un tel succès ces dernières années. Sur le long terme, le succès des modèles OpenACC et OpenMP dépendra très probablement de la capacité des différents fournisseurs d’accélérateurs à trouver le bon compromis entre performance et flexibilité. Qui vivra verra…

Bons développements !

Navigation

<12>

© HPC Today 2024 - All rights reserved.

Thank you for reading HPC Today.

Express poll

Do you use multi-screen
visualization technologies?

Industry news

Brands / Products index