thesis/src/alsk/1_relwork.tex

161 lines
9.8 KiB
TeX

\section{Travaux connexes}
Les squelettes algorithmiques, introduits par \autocite{ref:cole1989}, proposent une solution de
parallélisation assistée.
L'objectif des squelettes est de masquer l'implémentation parallèle d'un algorithme en fournissant
une interface permettant au développeur de choisir parmi des patrons de conception celui qui
correspond à son besoin.
Un avantage des squelettes par rapport à des interfaces plus simples, comme un ensemble de fonctions
correspondant directement aux différents patrons, réside dans leur capacité à être
composés~\autocite{ref:benoit2005}.
\autocite{ref:cole1989} les définit comme des fonctions d'ordre supérieur, c'est-à-dire des
fonctions capables d'utiliser des fonctions comme paramètres ou retournant d'autres fonctions.
L'exemple de la fonction d'ordre supérieur \og map \fg{} y est donné ainsi :
\begin{align*}
map : (a \to b) \to ([a] \to [b]).
\end{align*}
Il s'agit d'une fonction acceptant une autre fonction en paramètre et retournant une nouvelle
fonction.
Cette dernière agit sur un vecteur d'éléments du type de l'unique paramètre de la fonction unaire en
entrée ($a$) et retournant un vecteur d'éléments du même type que ce qui est retourné par la
fonction en entrée ($b$).
Pour produire le vecteur de type $[b]$, la fonction en entrée est appliquée sur chaque élément du
vecteur de type $[a]$.
Des fonctions d'ordre supérieur ont été présentées dans \acref{ch:mp},
\cref{subsubsec:mp/mpt/et/curry}.
De nombreux travaux ont proposé des modèles et implémentations de squelettes algorithmiques dans
différents langages de programmation depuis l'introduction de ce concept.
Ceux-ci proposent généralement des patrons classiques~\autocite{ref:campbell1996} : parmi ceux
orientés vers la parallélisation de données, on trouve \en{map}, \en{zip}, \en{reduce} ; et parmi
ceux travaillant sur la parallélisation de tâches, il existe \en{farm}, \en{pipeline}, \en{divide
and conquer}~\autocite{ref:kuchen2002a}.
Cette section illustre certains patrons.
Si l'on considère, dans un premier temps, le patron \en{fork-join} qui consiste simplement en
l'exécution de deux tâches $T_0$ et $T_1$ en parallèle, on peut utiliser la représentation faite
dans \acref{fig:alsk/relwork/forkjoin}.
Dans celle-ci, les tâches exécutées sont représentées par des blocs et nommées $T_i$$i$ est un
entier en l'absence duquel toutes les tâches $T$ effectuent la même chose.
Les zones englobant plusieurs tâches indiquent une exécution parallèle de celles-ci.
\begin{figure}
\inputfig{relwork/forkjoin}
{Patron d'exécution parallèle \en{fork-join}}
\end{figure}
Le patron \en{map} a déjà été abordé dans l'introduction de ce chapitre et correspond à l'exécution
de $n$ instances d'une tâche $T$ sur $n$ données, comme illustré par \acref{fig:alsk/relwork/map}.
En remplaçant l'ensemble défini de données par un flux, un \en{map} devient un \en{farm}.
\begin{figure}[!hb]
\inputfig{relwork/map}
{Patron d'exécution parallèle \en{map}}
\end{figure}
À nouveau comparable à \en{map}, la fonction d'ordre supérieure \en{zip} peut être définie ainsi :
\begin{align*}
zip : ((a, b) \to c) \to ([a], [b] \to [c]).
\end{align*}
Cela correspond donc à l'application d'une fonction d'arité \num{2} sur chaque élément de deux
vecteurs de données de même cardinalité ($\vert [a] \vert = \vert [b] \vert$) pour produire un
unique vecteur.
Il est possible de généraliser le principe à des arités quelconques.
L'application d'une fonction de projection (\texttt{min}, \texttt{sum}, ...) sur un ensemble de
données pour le réduire à une seule est appelée réduction, et le patron associé \en{reduce} ou plus
rarement \en{fold}.
Il est très souvent appliqué après un \en{map}, ce qui correspond alors au patron
\en{map-reduce}.% ref? illustré par \acref{fig:alsk/relwork/map}.
Le \en{pipeline} permet l'exécution parallèle de plusieurs tâches $T_0$, $T_1$, ... $T_n$ par
lesquelles les données en entrée passent successivement (\cref{fig:alsk/relwork/pipeline}).
Le principe de \og diviser pour régner \fg, en anglais \en{divide and conquer}, est de répartir le
travail initial entre les travailleurs par séparation successives de l'ensemble de données en
entrée.
Après cette étape, les résultats sont fusionnés successivement jusqu'à obtenir un résultat final.
Il est possible de diviser les données jusqu'à atteindre un niveau atomique (sans fixer le nombre de
travailleurs) ou de définir la profondeur maximale (limitant le nombre de travailleurs).
\begin{figure}
\inputfig{relwork/pipeline}
{Patron d'exécution parallèle \en{pipeline}}
\end{figure}
Les structures de contrôle classiques (branches conditionnelles, boucles, ...) peuvent également
être proposées comme squelette.
Il existe d'autres patrons, mais ceux qui ont été présentés ci-dessus et les combinaisons
concevables entre ceux-ci sont plus que suffisants pour le propos de cette thèse.
La bibliothèque C++ \gls{TBB} propose des fonctions correspondant à divers patrons parallèles tels
que \cppinline{parallel_for}, \cppinline{parallel_reduce}, \cppinline{parallel_pipeline}, ...
Ces fonctions sont conçues de manière similaire à la bibliothèque standard du langage (génériques
par template, utilisation d'itérateurs).
L'exécution est parallélisée et équilibrée dynamiquement, et \gls{TBB} détecte l'utilisation
imbriquée de plusieurs de ses fonctions.
Cette fonctionnalité permet de distinguer cette bibliothèque d'un simple ensemble de fonctions et la
rapproche des squelettes algorithmiques.
De manière similaire, le langage de programmation Cilk++ implémente des mots-clés pour faciliter
l'exécution parallèle ainsi qu'une structure de contrôle utilisant la syntaxe de la boucle
\cppinline{for} du C++, \cppinline{cilk_for}.
Ainsi, bien que cela ne soit pas un outil dont la conception repose spécifiquement sur les
squelettes algorithmiques (à l'instar de \gls{TBB}), les éléments proposés s'en rapprochent.
En revanche, \autocite{ref:rieger2019} propose un langage, Musket, fondamentalement basé sur le
concept de squelettes algorithmiques.
Celui-ci est un \gls{DSL}, dont la syntaxe est volontairement proche de celle du C++, l'objectif
étant de permettre la génération de code C++ parallèle.
Des types de données spécifiques, permettant la parallélisation, sont fournis (des types primitifs
jusqu'aux matrices).
Les motifs de parallélisation proposés sont \en{map}, \en{reduce}, \en{zip} et \en{shift partition}.
Les auteurs de Musket ont détaillé les raisons pour lesquelles ils ont choisi de développer un
langage plutôt qu'une bibliothèque~\autocite{ref:wrede2020}.
Celles-ci entraînent un surcoût en temps d'exécution par rapport à une implémentation manuelle ou
une génération de code comme peut le faire le compilateur d'un langage.
D'autre part, les bibliothèques étant contraintes à respecter la syntaxe du langage hôte, il est
plus facile de proposer une meilleure lisibilité du code au sein d'un \gls{DSL}.
Enfin, cette dernière contrainte apporte un autre argument au choix d'un langage dédié : cela permet
davantage de flexibilité dans les transformations appliquées sur le code pour produire un programme
parallèle.
De nombreuses bibliothèques de squelettes algorithmiques
ont été proposées~\autocite{ref:aldinucci2009,ref:ciechanowicz2009,ref:leyton2010,ref:legaux2013,ref:ernstsson2018,ref:philippe2019}.
\autocite{ref:striegnitz2000} a montré que le langage C++ permettait probablement l'implémentation
de bibliothèques de squelettes algorithmiques.
Plus tard, \autocite{ref:falcou2006a} l'a prouvé et a également montré que l'utilisation des
templates permettait d'obtenir un moindre surcoût en temps d'exécution.
La différence notable entre une bibliothèque \og classique \fg{} et ce que permettent les templates
en C++ est la possibilité de réellement agir durant la compilation, les rendant \og actives
\fg~\autocite{ref:veldhuizen1998a}.
Enfin, il est possible de proposer un \gls{DSL} au sein de certains langages existants, comme c'est
le cas du C++ : on parle alors d'un \gls{EDSL}~\autocite{ref:saidani2009}.
Cela permet de mitiger la scission entre langage et bibliothèque en termes de performances et de
flexibilité, au moins dans le cadre du C++.
Les travaux effectués durant cette thèse sur les squelettes algorithmiques ont ainsi été orientés
vers le C++, et plus spécifiquement vers l'implémentation de bibliothèques faisant usage de la
métaprogrammation template de ce langage.
Les inconvénients des bibliothèques par rapport à la création d'un langage ou même par rapport à
l'implémentation d'une extension pour un compilateur sont amoindris par l'utilisation de la
métaprogrammation.
En revanche, une bibliothèque apporte des avantages que nous avons jugé importants.
Premièrement, elles s'inscrivent dans un cadre qui peut déjà être connu par le développeur, et dans
le cas du langage C++, qui est très vastement utilisé, c'est particulièrement le cas.
L'implémentation d'un nouveau langage nécessite également la mise en place des nombreuses
optimisations déjà présentes et à venir sur les compilateurs de langages existants, alors qu'une
bibliothèque profitera de celles-ci automatiquement.
Certes, un nouveau langage peut, plutôt que d'être compilé directement vers de l'assembleur, générer
du code dans un autre langage bénéficiant de ces optimisations~\autocite{ref:rieger2019}, cependant,
cela requiert une maintenance parallèle des deux langages.
Il existe également le cas des extensions pour un compilateur, mais celles-ci doivent alors être
implémentées pour chaque compilateur, ou bien imposer aux utilisateurs de n'utiliser qu'un
sous-ensemble des compilateurs disponibles.
Une bibliothèque dont l'implémentation ne repose que sur le standard du langage ne crée aucune de
ces contraintes.