thesis/src/alsk/7_results.tex

299 lines
17 KiB
TeX

\section{Performances}
%{{{ Introduction "
Cette section présente des mesures de temps d'exécution de programmes utilisant la bibliothèque
présentée dans ce chapitre.
Toutes les mesures ont été effectuées sur une machine dotée d'un Intel Xeon E7-8890 v3, cadencé à
\SI{2.5}{\GHz} et ayant \num{72} cœurs physiques (\num{18} processeurs ayant chacun \num{4} cœurs
physiques).
Les programmes sont compilés en utilisant GCC (\texttt{g++}) 8.2.0 avec notamment le pack
d'optimisations \texttt{O2}.
Les valeurs données dans ce document sont obtenues par une moyenne sur \num{20} exécutions en
utilisant des états initiaux différents pour les \gls{PRNG} (les mêmes \num{20} états sont utilisés
à chaque fois).
Lorsque cela est pertinent (notamment, suffisamment visible), l'intervalle de confiance à
\SI{99}{\percent} est affiché.
Pour ce qui est des exécutions, nous avons réglé l'affinité du processus de sorte que pour chaque
processeur, au maximum un cœur soit utilisé.
Cela limite donc à \num{18} cœurs pour protéger d'un biais de mesure potentiel dû à l'utilisation
simultanée de plusieurs cœurs d'un même processeur.
Un objectif important que devait atteindre cette bibliothèque est d'être compétitive, en termes de
temps d'exécution par rapport à une solution écrite directement par un développeur.
En effet, une abstraction est moins intéressante si elle implique un fort surcoût.
Pour cette raison, nous avons utilisé le langage C++ et la métaprogrammation template qui
permettent, lorsqu'ils sont correctement utilisés, de s'approcher d'une implémentation manuelle en
ce qui concerne le temps d'exécution.
Pour évaluer cela, nous avons comparé les temps d'exécution de programmes écrits en utilisant la
bibliothèque (et en utilisant différentes politiques d'exécution) par rapport aux temps d'exécution
de programmes équivalents écrits sans utiliser cette bibliothèque.
Ces programmes implémentent un \graspels{} dans le but de résoudre des instances de \gls{TSP} de
\num{38} sommets et de \num{194} sommets, ils utilisent donc des nombres pseudo-aléatoires.
Afin de s'assurer de la pertinence des temps mesurés, nous avons donc fait en sorte de garantir la
répétabilité des programmes écrits sans utiliser la bibliothèque.
Pour les versions l'utilisant, la répétabilité est obtenue sans effort.
Dans cette section, nous utiliserons trois variables $N$, $O$ et $I$ qui correspondront
respectivement au nombre d'itérations (parallélisables) du \gls{GRASP}, au nombre d'itérations (non
parallélisables) de la boucle extérieure de l'\gls{ELS} et au nombre d'itérations (parallélisables)
de la boucle intérieure de l'\gls{ELS}.
Toutes les mesures sont effectuées pour quatre politiques d'exécutions différentes lorsque la
bibliothèque est utilisée :
\begin{itemize}
\item \og firstlevel \fg, qui ne parallélise que le premier niveau pouvant être exécuté en
parallèle ;
\item \og staticpool \fg, qui utilise un \en{thread pool} en associant à une tâche un
\en{thread} en fonction de son identifiant et en équilibrant les multiples niveaux parallèles
comme décrit dans \acref{sec:alsk/exec} ;
\item \og dynamicpool \fg, qui utilise un \en{thread pool} utilisé de manière classique ;
\item \og thread \fg, qui applique l'équilibrage des multiples niveaux parallèles de
\acref{sec:alsk/exec} en créant des \en{threads} dynamiquement.
\end{itemize}
%}}}
%{{{ Sequential "
Bien que l'objectif principal de cette bibliothèque soit d'aider à l'écriture de programmes
parallèles, elle peut également être utilisée pour produire des programmes séquentiels en
choisissant une politique d'exécution appropriée.
Cela peut par exemple être utile à des fins de débugage, mais aussi pour effectuer des mesures de
performance dans certains domaines scientifiques où les comparaisons sont généralement effectuées
sur des programmes séquentiels.
Nous avons donc dans un premier temps mesuré les temps d'exécution de programmes séquentiels.
\Acref{fig:alsk/results/rt_graspels_dj38_24_20_20_seq,fig:alsk/results/rt_graspels_qa194_24_20_20_seq}
montrent les temps d'exécution de programmes séquentiels avec $N = 24$, $O = 20$ et $I = 20$.
Les étiquettes préfixées de \og hw \fg{} correspondent aux programmes écrits sans utiliser la
bibliothèque tandis que celles préfixées de \og sk \fg{} indiquent ceux l'utilisant.
L'étiquette \og hw\_seq \fg{} est associée au programme écrit pour une exécution séquentielle.
Quant à \og hw\_par \fg{}, il s'agit d'un programme écrit pour une exécution parallèle, bien que
l'exécution sera effectivement séquentielle puisqu'un seul cœur est affecté.
Quelle que soit la politique d'exécutions utilisée pour les versions employant les squelettes
algorithmiques, l'exécution sera également obligatoirement séquentielle en pratique puisqu'un seul
cœur est affecté comme pour \og hw\_par \fg{}.
\Acref{fig:alsk/results/rt_graspels_dj38_24_20_20_seq} correspond aux temps d'exécution pour une
instance de \gls{TSP} ayant \num{38} sommets et il s'agit d'une instance ayant \num{194} sommets
pour \acref{fig:alsk/results/rt_graspels_qa194_24_20_20_seq}.
Pour une petite instance (\num{38} sommets), on observe de légères variations, cependant la courte
durée d'exécution ne permet pas de conclure clairement, sinon que l'utilisation de la bibliothèque
n'engendre pas de surcoût significatif même pour des tâches courtes.
Les temps mesurés pour la seconde instance (\num{194} sommets) sont particulièrement proches pour
les programmes écrits sans et avec la bibliothèque à l'exception des politiques d'exécution \og
staticpool \fg{} et \og thread \fg.
Ainsi, à nouveau, on observe que la politique d'exécution séquentielle permet d'obtenir des
exécutions de durées quasiment identiques à ce que l'on peut atteindre sans utiliser la
bibliothèque.
À l'inverse, l'utilisation d'une autre politique d'exécution dans un contexte séquentiel (parce
qu'un seul cœur est disponible) peut en revanche être coûteux.
En effet, celles-ci peuvent avoir besoin de mettre en place des mécanismes, lesquels ne sont
généralement pas optimisés pour le cas d'une exécution séquentielle.
Afin de garantir la répétabilité y compris lorsqu'un seul \en{thread} est possible, l'implémentation
des politiques d'exécution doit dans ce cas également produire un comportement cohérent, privant
celle-ci d'éventuelles optimisations.
Cela explique par ailleurs pourquoi la bibliothèque n'utilise pas automatiquement la politique
d'exécution séquentielle lorsque le nombre de cœurs alloués est de \num{1}.
Néanmoins, dans le cas où la répétabilité n'a pas besoin d'être assurée au point d'avoir le même
déroulement entre une exécution séquentielle et une exécution parallèle, alors il est avisé de la
part du développeur d'utiliser la politique d'exécution séquentielle lorsqu'il n'y a qu'un cœur.
\begin{figure}
\centering
\includegraphics{img/alsk/rt_graspels_dj38_24_20_20_seq.pdf}
\caption{Temps d'exécution séquentielle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{38} sommets}
\label{fig:alsk/results/rt_graspels_dj38_24_20_20_seq}
\end{figure}
\begin{figure}
\centering
\includegraphics{img/alsk/rt_graspels_qa194_24_20_20_seq.pdf}
\caption{Temps d'exécution séquentielle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets}
\label{fig:alsk/results/rt_graspels_qa194_24_20_20_seq}
\end{figure}
%}}}
%{{{ Parallel "
%{{{ var cores "
\Acref{fig:alsk/results/rt_graspels_dj38_24_20_20_par,fig:alsk/results/rt_graspels_qa194_24_20_20_par}
présentent les temps d'exécution de programmes parallèles avec $N = 24$, $O = 20$ et $I = 20$.
La première étiquette, \og hw\_par \fg, correspond au programme écrit sans utiliser la
bibliothèque pour une exécution parallèle.
Les étiquettes préfixées par \og sk \fg{} correspondent à des programmes écrits en utilisant la
bibliothèque et en utilisant différentes politiques d'exécution.
La seconde partie de chaque étiquette indique quelle politique d'exécution est utilisée.
Pour une instance de \gls{TSP} ayant \num{38} sommets, on obtient les résultats présentés par
\acref{fig:alsk/results/rt_graspels_dj38_24_20_20_par}.
On observe globalement des performances similaires et surtout une décroissance stable du temps
d'exécution avec l'augmentation du nombre de cœurs.
La politique d'exécution \og staticpool \fg{} semble particulièrement efficace.
Cela se justifie par l'absence de sections critiques (contrairement à ce que l'on a avec \og
dynamicpool \fg) et la réutilisation des \en{threads} créés (contrairement à ce que fait \og
thread \fg).
La répartition des tâches est également un peu meilleure que celle de \og firstlevel \fg{} pour les
dernières itérations de la boucle principale du \gls{GRASP} lorsqu'il y a un reste à la division de
$N$ par le nombre de cœurs disponibles.
\begin{figure}
\centering
\includegraphics{img/alsk/rt_graspels_dj38_24_20_20_par.pdf}
\caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{38} sommets}
\label{fig:alsk/results/rt_graspels_dj38_24_20_20_par}
\end{figure}
Lorsque l'on travaille sur une instance de \gls{TSP} avec \num{194} sommets, on observe les
résultats présentés dans \acref{fig:alsk/results/rt_graspels_qa194_24_20_20_par}.
Ils sont dans l'ensemble équivalents aux précédents, ce qui confirme une certaine indépendance des
performances obtenues par rapport à la taille des données traitées.
\begin{figure}
\centering
\includegraphics{img/alsk/rt_graspels_qa194_24_20_20_par.pdf}
\caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets}
\label{fig:alsk/results/rt_graspels_qa194_24_20_20_par}
\end{figure}
%}}}
%{{{ var grasp_n "
En faisant varier $N$ de \num{4} à \num{20} par pas de \num{4}, on obtient les courbes de
\acref{fig:alsk/results/rt_graspels_qa194_4:20_20_20_par}.
En particulier pour $N=4$ (\cref{fig:alsk/results/rt_graspels_qa194_4_20_20_par}), $N=8$
(\cref{fig:alsk/results/rt_graspels_qa194_8_20_20_par}) et $N=12$
(\cref{fig:alsk/results/rt_graspels_qa194_12_20_20_par}) pour lesquels c'est très visible, on
observe une limite dans l'accélération obtenue pour \og firstlevel \fg.
Ce résultat est logique puisque la politique d'exécution utilisée dans ce cas ne parallélisant que
le premier niveau possible, si celui-ci correspond à une boucle de $k$ itérations l'accélération
maximale que l'on peut obtenir est de $k$.
Les autres politiques d'exécution se comportent de la même manière, indépendamment du nombre
d'itérations du \gls{GRASP}, ce qui est attendu puisque toutes les trois sont capables de
paralléliser de multiples niveaux et donc de tirer profit des itérations parallélisables de
l'\gls{ELS} (au nombre fixe de $I=20$).
\begin{figure}
\centering
\foreach \n in {4,8,...,20} {
\begin{subfigure}[b]{.49\textwidth}
\centering
\includegraphics{img/alsk/rt_graspels_qa194_v\n_20_20_par.pdf}
\caption{$N = \n$}
\label{fig:alsk/results/rt_graspels_qa194_\n_20_20_par}
\end{subfigure}
\hfill
}
\begin{subfigure}[b]{.49\textwidth}
\centering
\hspace{2em}
\includegraphics{img/alsk/rt_graspels_qa194_var_grasp_par_legend.pdf}
\vspace{12ex}
\end{subfigure}
\caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets selon
le nombre d'itérations du \glsxtrshort{GRASP}}
\label{fig:alsk/results/rt_graspels_qa194_4:20_20_20_par}
\end{figure}
%}}}
%{{{ var els iter max "
Nous avons voulu vérifier l'effet de la variation du nombre d'itérations de la boucle centrale non
parallélisable (la boucle extérieure de l'\gls{ELS} dont le nombre d'itérations est $O$).
Les courbes présentées dans \acref{fig:alsk/results/rt_graspels_qa194_4_1:50_20_par} correspondent à
des mesures de temps d'exécution pour $N=4$ et $O$ variant entre $1$ et $50$.
Ces courbes permettent d'observer que d'une manière globale, le comportement reste similaire.
\begin{figure}
\centering
\foreach \n in {1,4,8,40,50} {
\begin{subfigure}[b]{.49\textwidth}
\centering
\includegraphics{img/alsk/rt_graspels_qa194_4_v\n_20_par.pdf}
\caption{$O = \n$}
\label{fig:alsk/results/rt_graspels_qa194_4_\n_20_par}
\end{subfigure}
\hfill
}
\begin{subfigure}[b]{.49\textwidth}
\centering
\hspace{2em}
\includegraphics{img/alsk/rt_graspels_qa194_var_els_iter_max_par_legend.pdf}
\vspace{12ex}
\end{subfigure}
\caption{Temps d'exécution parallèle d'un \graspels{} pour une instance de \glsxtrshort{TSP} de \num{194} sommets selon
le nombre d'itérations non parallélisables de l'\glsxtrshort{ELS}}
\label{fig:alsk/results/rt_graspels_qa194_4_1:50_20_par}
\end{figure}
%}}}
%{{{ speedup "
Enfin, sur la base des données précédentes,
\acref{fig:alsk/results/rt_graspels_qa194_4_20_20_speedup,fig:alsk/results/rt_graspels_qa194_20_20_20_speedup}
présentent l'accélération obtenue pour les différentes politiques d'exécution (ainsi que pour une
parallélisation sans utiliser la bibliothèque) en fonction du nombre de cœurs alloués.
\Acref{fig:alsk/results/rt_graspels_qa194_4_20_20_speedup} correspond à l'exécution d'un \graspels{}
pour lequel $N=4$, c'est-à-dire que la boucle principale du \gls{GRASP} effectue $4$ itérations qui
peuvent être parallélisées.
Les valeurs de $O$ et $I$ sont conservées à la valeur par défaut utilisée durant cette section, à
savoir $20$ pour les deux.
Lorsque $N=4$, on observe en particulier que la parallélisation en utilisant une politique
d'exécution ne traitant qu'un niveau est limitée en accélération à la valeur atteinte lorsque le
nombre de cœurs alloués égale $N$, ce qui n'est pas surprenant.
Par ailleurs, l'accélération obtenue en utilisant la politique d'exécution par \en{thread pool} \og
statique \fg{} semble être généralement meilleure que les autres.
Dans tous les cas, on observe que l'accélération croît effectivement lorsque le nombre de cœurs
alloués croît.
\begin{figure}
\ocgfigIG{Accélération en fonction du nombre de coeurs alloués}
{results/rt_graspels_qa194_4_20_20_speedup}{rt_graspels_qa194_}{_20_20_speedup.pdf}
{Accélération en fonction du nombre de cœurs alloués (\graspels{} avec $N=4$, \glsxtrshort{TSP} de \num{194} sommets)}
{$N$ :}{\N}{4/on,8/off,12/off,16/off}
\end{figure}
\Acref{fig:alsk/results/rt_graspels_qa194_20_20_20_speedup} correspond quant à elle à l'exécution
d'un \graspels{} avec $N=20$.
Le comportement global est conservé et l'accélération obtenue est proportionnelle au nombre de cœurs
alloués.
Pour la politique d'exécution \og sk\_firstlevel \fg{}, l'accélération obtenue n'atteint logiquement
plus un plafond puisque le nombre de cœurs alloués n'atteint pas, durant ces mesures, la valeur de
$N$.
\begin{figure}
\centering
\includegraphics{img/alsk/rt_graspels_qa194_20_20_20_speedup.pdf}
\caption{Accélération en fonction du nombre de cœurs alloués (\graspels{} avec $N=20$,
\glsxtrshort{TSP} de \num{194} sommets)}
\label{fig:alsk/results/rt_graspels_qa194_20_20_20_speedup}
\end{figure}
\Acref{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_1,fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_16}
montrent l'accélération obtenue en fonction de la valeur de $N$ pour un nombre de cœurs alloués
défini, respectivement \num{1} et \num{18}.
Dans \acref{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_1}, on observe donc le comportement
des politiques d'exécution lorsqu'elles sont utilisées pour une exécution séquentielle.
Cela confirme les remarques précédentes à propos de la politique \og sk\_staticpool \fg{} qui
apparaît comme étant la moins efficace dans ce contexte précis.
\begin{figure}
\centering
\includegraphics{img/alsk/rt_graspels_qa194_all_20_20_speedup_1.pdf}
\caption{Accélération en fonction de $N$ pour \num{1} cœur alloué (\graspels{}, \glsxtrshort{TSP} de \num{194} sommets)}
\label{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_1}
\end{figure}
En revanche, dans \acref{fig:alsk/results/rt_graspels_qa194_all_20_20_speedup_16} on vérifie que
pour toute valeur de $N$ cette politique d'exécution est meilleure.
Cela semble indiquer que le fait d'effectuer une partie du travail durant la compilation et de
déterminer à l'avance à quel \en{thread} affecter chaque tâche affecte favorablement les
performances du programme.
\begin{figure}
\ocgfigIG{Accélération en fonction du nombre d'itérations}
{results/rt_graspels_qa194_all_20_20_speedup_16}{rt_graspels_qa194_all_20_20_speedup_}{.pdf}
{Accélération en fonction de $N$ pour \num{18} cœurs alloués (\graspels{}, \glsxtrshort{TSP} de \num{194} sommets)}
{nombre de cœurs :}{\T}{2/off,4/off,6/off,8/off,10/off,12/off,14/off,16/off,18/on}
\end{figure}
%}}}
%}}}