thesis version

This commit is contained in:
Alexis Pereda 2021-10-06 21:08:28 +02:00
commit 6cc2e76b2a
400 changed files with 19539 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

88
CMakeLists.txt Normal file
View File

@ -0,0 +1,88 @@
cmake_minimum_required(VERSION 3.1)
project(thesis)
set(LATEX_COMPILER_FLAGS "-interaction=batchmode -file-line-error -shell-escape"
CACHE STRING "Flags passed to latex.")
include(UseLATEX.cmake)
# Global
set(LATEX_USE_SYNCTEX ON)
set(imgdir img)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pdf)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/buildfig)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/figures)
# Functions
macro(subdirlist result curdir)
file(GLOB children RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${curdir}/*)
foreach(child ${children})
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${child})
subdirlist(${result} ${child})
list(APPEND ${result} ${child})
endif()
endforeach()
endmacro()
set(imgdirs ${imgdir})
subdirlist(imgdirs ${imgdir})
# Document
set(modes oneside twoside print genfigures)
set(srcdir src)
set(stydir sty)
file(GLOB_RECURSE src RELATIVE ${CMAKE_SOURCE_DIR} ${srcdir}/*.tex)
file(GLOB_RECURSE bib RELATIVE ${CMAKE_SOURCE_DIR} ${srcdir}/*.bib)
file(GLOB_RECURSE sty RELATIVE ${CMAKE_SOURCE_DIR} ${stydir}/*.sty)
set(latex_document_options)
set(custom_command_options ALL)
foreach(mode ${modes})
set(maintex main_${mode})
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink
${CMAKE_CURRENT_BINARY_DIR}/${srcdir}/${maintex}.tex
${CMAKE_CURRENT_BINARY_DIR}/${maintex}.tex
)
add_latex_document(
${srcdir}/main_${mode}.tex
INPUTS ${src} ${lst} ${sty}
BIBFILES ${bib}
IMAGE_DIRS ${imgdirs}
USE_BIBLATEX
USE_GLOSSARY
${latex_document_options}
)
if(NOT ${mode} STREQUAL genfigures)
set(synctex_file ${CMAKE_PROJECT_NAME}_${mode}.synctex.gz)
add_custom_target(pdf_${synctex_file} ${custom_command_options}
DEPENDS ${maintex}.pdf
COMMAND ${CMAKE_COMMAND} -E copy ${maintex}.synctex.gz pdf/${synctex_file}
)
set(pdf_file ${CMAKE_PROJECT_NAME}_${mode}.pdf)
add_custom_target(pdf_${pdf_file} ${custom_command_options}
DEPENDS ${maintex}.pdf
COMMAND ${CMAKE_COMMAND} -E copy ${maintex}.pdf pdf/${pdf_file}
)
add_custom_target(pdf_${CMAKE_PROJECT_NAME}_${mode} DEPENDS pdf_${synctex_file} pdf_${pdf_file})
endif()
set(latex_document_options EXCLUDE_FROM_ALL)
set(custom_command_options)
endforeach()
add_custom_target(generate_figures
DEPENDS main_genfigures_pdf
)
add_custom_command(TARGET generate_figures POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/buildfig/*.pdf
${CMAKE_CURRENT_BINARY_DIR}/figures
)

109
README.md Normal file
View File

@ -0,0 +1,109 @@
# About
Thesis title: using template metaprogramming to design active libraries for assisted parallelisation.
(French: *application de la métaprogrammation template à la conception de bibliothèques actives
de parallélisation assitée*)
## Download
- [Computer version](https://phd.pereda.fr/assets/thesis/alexis_pereda_thesis.pdf);
- [Print version](https://phd.pereda.fr/assets/thesis/alexis_pereda_thesis_print.pdf).
## Abstract
<div align="justify">
Hardware performance has been increasing through the addition of computing cores rather than through
increasing their frequency since the early 2000s.
This means that parallel programming is no longer optional should you need to make the best use of
the hardware at your disposal.
Yet many programs are still written sequentially: parallel programming introduces numerous
difficulties.
Amongst these, it is notably hard to determine whether a sequence of a program can be executed in
parallel, i.e. preserving its behaviour as well as its overall result.
Even knowing that it is possible to parallelise a piece of code, doing it correctly is another
problem.
In this thesis, we present two approaches to make writing parallel software easier.
We present an active library (using C++ template metaprogramming to operate during the compilation
process) whose purpose is to analyse and parallelise loops.
To do so, it builds a representation of each processed loop using expression templates through an
embedded language.
This allows to know which variables are used and how they are used.
For the case of arrays, which are common within loops, it also acquires the index functions.
The analysis of this information enables the library to identify which instructions in the loop can
be run in parallel.
Interdependent instructions are detected by knowing the variables and their access mode for each
instruction.
Given a group of interdependent instructions and the known index functions, the library decides if
the instructions can be run in parallel or not.
We want this library to help developers writing loops that will be automatically parallelised
whenever possible and run sequentially as without the library otherwise.
Another focus is to provide this to serve as a framework to integrate new methods for parallelising
programs and extended analysis rules.
We introduce another active library that aims to help developers by assisting them in writing
parallel software instead of fully automating it.
This library uses algorithmic skeletons to let the developer describe its algorithms with both its
sequential and parallel parts by assembling atomic execution patterns such as a series of tasks or a
parallel execution of a repeated task.
This description includes the data flow, that is how parameters and function returns are
transmitted.
Usually, this is automatically set by the algorithmic skeleton library, however it gives the
developer greater flexibility and it makes it possible, amongst other things, for our library to
automatically transmit special parameters that must not be shared between parallel tasks.
One feature that this allows is to ensure repeatability from one execution to another even for
stochastic algorithms.
Considering the distribution of tasks on the different cores, we even reduce the number of these
non-shared parameters.
Once again, this library provides a framework at several levels.
Low-level extensions consist of the implementation of new execution patterns to be used to build
skeletons.
Another low-level axis is the integration of new execution policies that decide how tasks are
distributed on the available computing cores.
High-level additions will be libraries using ours to offer ready-to-use algorithmic skeletons for
various fields.
</div>
Keywords: template metaprogramming; assisted parallelisation; automatic parallelisation; active libraries; algorithmic skeletons; repeatability.
## Related publications
- "Repeatability with Random Numbers Using Algorithmic Skeletons", ESM 2020 (https://hal.archives-ouvertes.fr/hal-02980472);
- "Modeling Algorithmic Skeletons for Automatic Parallelization Using Template Metaprogramming", HPCS 2019 (IEEE) [10.1109/HPCS48598.2019.9188128](https://doi.org/10.1109/HPCS48598.2019.9188128);
- "Processing Algorithmic Skeletons at Compile-Time", ROADEF 2020 (https://hal.archives-ouvertes.fr/hal-02573660);
- "Algorithmic Skeletons Using Template Metaprogramming", ICAST 2019;
- "Parallel Algorithmic Skeletons for Metaheuristics", ROADEF 2019 (https://hal.archives-ouvertes.fr/hal-02059533);
- "Static Loop Parallelization Decision Using Template Metaprogramming", HPCS 2018 (IEEE) [10.1109/HPCS.2018.00159](https://doi.org/10.1109/HPCS.2018.00159).
## Related projects
- [AlSk](https://phd.pereda.fr/dev/alsk), an algorithmic skeletons active library;
- [pfor](https://phd.pereda.fr/dev/pfor), an automatic parallelisation active library;
- [ROSA](https://phd.pereda.fr/dev/rosa), an algorithmic skeletons collection for [OR](https://en.wikipedia.org/wiki/Operations_research) algorithms;
- [TMP](https://phd.pereda.fr/dev/tmp), template metaprogramming library used to implement this library.
## Usage
To produce the `Makefile`:
```bash
mkdir build
cd build
cmake ..
```
Compilation has been tested with `texlive-full` version 2020.20210202-3.
To build the project:
```
make
```
Be patient, it takes *some* time.
Make can be run with these arguments:
- `pdf_thesis_oneside`: to build the "computer" version, including dynamic figures (default);
- `pdf_thesis_twoside`: same as oneside but better for double-page display;
- `pdf_thesis_print`: printing version (no dynamic figures, double-page and blank pages where required).
PDF files are generated in `build/pdf/`.

2074
UseLATEX.cmake Normal file

File diff suppressed because it is too large Load Diff

BIN
img/alsk/exptt.pdf Normal file

Binary file not shown.

BIN
img/alsk/lineartt.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
img/gnx/placeholder Normal file
View File

1
img/mp/placeholder Normal file
View File

@ -0,0 +1 @@
-- only to force git to track parent directory --

BIN
img/par/raymarching.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
img/pfor/fixediv.pdf Normal file

Binary file not shown.

BIN
img/pfor/fixedv.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_cores.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_1.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_10.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_12.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_14.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_16.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_18.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_2.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_4.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_6.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_par_8.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_seq.pdf Normal file

Binary file not shown.

BIN
img/pfor/rt_speedup.pdf Normal file

Binary file not shown.

92
merge Executable file
View File

@ -0,0 +1,92 @@
#!/bin/bash
[ ! -d .git ] && exit 1
opt=$1; shift
chapters="par gnx mp pfor alsk"
subdirs="alg fig lst"
acronyms=
commands=
for subdir in $(echo ${subdirs}); do
rm -rf "src/${subdir}/*"
done
rm -f 'src/tikz/*'
# gather chapter sources
chn=0
cp '../remerciements/src/thanks.tex' "src/${chn}_1_thanks.tex"
for chapter in $(echo ${chapters}); do
rm -rf "src/${chapter}"
cp -rf "../${chapter}/src/${chapter}" 'src'
chn=$((chn+1))
if [ "${opt}" != 'contents' ]; then
cp "../${chapter}/src/${chapter}.tex" "src/${chn}_${chapter}.tex"
fi
rm -rf "img/${chapter}"
cp -rf "../${chapter}/img/${chapter}" 'img'
cp "../${chapter}/src/tikz/"* 'src/tikz/'
for subdir in $(echo ${subdirs}); do
dir="../${chapter}/src/${subdir}/${chapter}"
if [ -d "${dir}" ]; then
cp -rf "${dir}" "src/${subdir}/"
fi
done
acronymsfile="../${chapter}/src/acronyms.tex"
if [ -f "${acronymsfile}" ]; then
acronyms="${acronyms} ${acronymsfile}"
fi
commandsfile="../${chapter}/src/commands.tex"
if [ -f "${commandsfile}" ]; then
commands="${commands} ${commandsfile}"
fi
done
# merge all acronyms files
letter=A
previous=
while read line; do
current=$(echo "${line}"|cut -d'{' -f2|cut -d'}' -f1)
curletter=$(echo "${current}"|cut -c1)
if [ "${curletter}" != "${letter}" ]; then
echo
letter="${curletter}"
fi
if [ "${current}" = "${previous}" ]; then
echo>&2 "[acronyms] incoherent duplicate for {${current}}"
fi
previous="${current}"
echo "${line}"
done<<<$(sort -u ${acronyms}|grep -Ev '^$'|sed 's,\\,\\\\,g')>src/acronyms.tex
# merge all commands files
declare -A arrcmds
for cmds in ${commands}; do
chapter=$(echo "${cmds}"|cut -d'/' -f2)
echo "%{{{ ${chapter} \""
while read line; do
cmd=$(sed -re 's/[^{]*\{([^}]+)\}.*/\1/'<<<"${line}")
if [ -n "${cmd}" ]; then
if [ ${arrcmds[${cmd}]+x} ]; then
if [ ${arrcmds[${cmd}]} != ${line} ]; then
echo>&2 "[commands] incoherent duplicate for ${cmd}"
fi
echo -n "% "
else
arrcmds[${cmd}]="${line}"
fi
echo "${line}"
fi
done<<<$(sed 's,\\,\\\\,g' ${cmds})
echo "%}}}"
done>src/commands.tex

71
src/0_0_titlepage.tex Normal file
View File

@ -0,0 +1,71 @@
\date{01/07/2021}
\def\university{Université Clermont Auvergne}
\def\unived{École doctorale des sciences pour l'ingénieur}
\def\jury{%
Alexandre \textsc{Guitton} & Professeur des universités & Université Clermont Auvergne & Président \\
Joël \textsc{Falcou} & Maître de conférences HDR & Université Paris-Saclay & Rapporteur \\
Françoise \textsc{Baude} & Professeur des universités & Université Côte d'Azur & Rapportrice \\
Éric \textsc{Innocenti} & Maître de conférences & Université de Corse & Examinateur \\
Bruno \textsc{Bachelet} & Maître de conférences HDR & Université Clermont Auvergne & Directeur de thèse\\
David \textsc{Hill} & Professeur des universités & Université Clermont Auvergne & Directeur de thèse\\
}
\def\juryinvited{%
Claude \textsc{Mazel} & Maître de conférences & Université Clermont Auvergne & Invité \\
Jian-Jin \textsc{Li} & Maître de conférences & Université Clermont Auvergne & Invitée \\
}
\makeatletter
\begin{titlepage}
\newgeometry{width=180mm}
\setlength\parskip{1.5ex}
\begin{center}
% Academic
\textsc{\large\university}
{\large\unived}
\vspace{5ex}
% Author
{\large Thèse présentée par}
{\Large\@author}
\vspace{5ex}
% Ph.D
en vue de l'obtention du grade de
{\LARGE Docteur d'université}
{\Large Spécialité : informatique}
\vspace{8ex}
% Title
\rule[2ex]{\textwidth}{1pt}
{\LARGE\@title}
\rule[0ex]{\textwidth}{1pt}
\vspace{8ex}
% Defense
{\large Soutenue publiquement le \@date{} devant le jury composé de :}
% {\large devant le jury composé de :}
\vspace{5ex}
\vfill
% Jury
\bgroup
\def\arraystretch{1.3}
\begin{tabular}{l >{\it}l l l}
\jury
\juryinvited
\end{tabular}
\egroup
\end{center}
\end{titlepage}
\makeatother

46
src/0_1_thanks.tex Normal file
View File

@ -0,0 +1,46 @@
\begin{thesisthanks}
Je remercie avant tout mes directeurs de thèse, Bruno Bachelet et David Hill.
Bruno pour avoir proposé ce sujet de thèse (spécialement pour la partie \og métaprogrammation \fg)
et m'avoir choisi pour travailler dessus, mais surtout pour son aide, sa disponibilité, sa
patience et sa confiance indéfectible durant ces quelques années.
David pour avoir rendu cette thèse possible et pour son aide indispensable quant à certaines
difficultés... scientifiques et administratives.
Je remercie Claude Mazel, co-encadrant qui n'aura eu de cesse de me vitupérer pour d'importantes
vétilles, aussi bien pour ses relectures que pour les nombreuses discussions que nous avons eues,
évidemment toujours expressément au sujet de la thèse.
Je remercie également Joël Falcou, Françoise Baude, Éric Innocenti, Alexandre Guitton ainsi que
Jian-Jin Li pour avoir fait partie de mon jury de thèse, et en particulier Joël et Françoise pour
leur travail de rapporteurs.
\cutout r (-4.5em,3.3ex)\Shapepar{\hexagonshape}
Ce document a été écrit en utilisant \LaTeX{} et en profitant de nombreux paquets sans lesquels ce
travail de rédaction aurait été bien moins appréciable.
Je tiens donc à remercier toute personne ayant participé au développement de ces outils, et plus
généralement à l'expansion du logiciel libre.
\indent Je remercie aussi l'ensemble des personnes travaillant au LIMOS et à l'ISIMA. Loïc,
notamment pour m'avoir épargné, de force quelques fois, certaines besognes en faveur de l'avancement
de la thèse ; Yves-Jean qui a toujours pris le temps de discuter de mathématiques, y compris
quelquefois utiles à mes travaux ; Fréd avec qui j'ai pu attendre Kaamelott ; l'équipe technique et
le personnel administratif qui m'ont facilité bien des démarches.
\hfill\break\indent
Merci aux quelques docteurs -- ou presque -- que j'ai cotoyé durant cette thèse pour l'avoir en
partie animée.
David et Kévin pour les multiples discussions autour de problèmes variés et les
\textit{occasionnelles} parties de billard ; Théo, futur artiste qui m'a évité bien des soliloques ;
et bien sûr Cyrille avec qui j'ai pu explorer différents domaines de l'informatique, des autres
sciences et de tant d'autres thèmes.
\begin{CJK}{UTF8}{min}
ICAST2019を開催した熊本大学とそれにかかわった人たちに感謝します。
特に岸田先生には滞在中お世話になり、初めての日本訪問を楽しむことができました。
\end{CJK}
Merci au théorème fondamental de l'ingénierie logicielle, à savoir que tout problème peut être résolu
en ajoutant un niveau d'indirection, d'être vrai, au moins dans le cadre des développements
effectués durant cette thèse.
J'étends enfin ces remerciements à mes proches, nommément Gwénaëlle qui a dû accepter un emploi du
temps chaotique, et m'a malgré cela soutenu inlassablement.
\end{thesisthanks}

133
src/0_2_abstract.tex Normal file
View File

@ -0,0 +1,133 @@
%{{{ résumé "
\begin{abstract}
%{{{
L'écriture de programmes parallèles, par opposition aux programmes \og classiques \fg{} séquentiels
et n'utilisant donc qu'un processeur, est devenue une nécessité.
En effet, si jusqu'au début du millénaire la puissance de calcul des ordinateurs dépendait
principalement de la fréquence du processeur, elle est maintenant liée au nombre de cœurs de calcul
qui sont de plus en plus nombreux.
Pourtant, à cause des difficultés introduites par la parallélisation, la plupart des programmes sont
toujours écrits de manière \og classique \fg.
En particulier, il peut être compliqué de déterminer, étant donné une sous partie d'un programme, si
la parallélisation est possible, c'est-à-dire si elle n'introduira pas un changement de comportement
du programme.
Cependant, même en sachant précisément ce qui peut être parallélisé, le faire correctement est aussi
une tâche difficile.
Cette thèse présente deux approches pour simplifier l'écriture de programmes parallèles.
Nous proposons une bibliothèque active -- par métaprogrammation template, elle agit durant la
compilation -- qui acquiert des informations à propos d'une portion de programme, correspondant à
une boucle, au moyen de patrons d'expression.
Celles-ci sont utilisées pour analyser les instructions et identifier lesquelles peuvent être
exécutées en parallèle.
Cette analyse repose sur deux niveaux de connaissance : l'ensemble des variables utilisées en
distinguant les accès en lecture de ceux en écriture, et, puisqu'il s'agit souvent de tableaux, des
fonctions d'indice.
Les variables et leur mode d'accès permettent de savoir quelles instructions sont interdépendantes
tandis que les fonctions d'indice nous servent à déterminer, pour un groupe d'instructions, s'il
est possible de procéder à une exécution parallèle des itérations de la boucle.
L'objectif de cette bibliothèque est de proposer un cadre, à la fois pour les développeurs afin
d'écrire des boucles qui seront automatiquement exécutées en parallèle si cela est possible, mais
aussi à un niveau plus élevé pour intégrer de nouvelles méthodes de parallélisation ou d'autres
règles à utiliser pour l'analyse.
Nous proposons également une seconde bibliothèque, active elle aussi, orientée sur la
parallélisation assistée en utilisant la technique des squelettes algorithmiques comme interface
pour le développeur.
Celle-ci permet de représenter des algorithmes complets comme des assemblages de motifs d'exécution
: séquence de tâches ; exécution répétée d'une tâche en parallèle ; ...
En utilisant cette connaissance, nous pouvons mettre en place des choix dans la manière de répartir
les tâches exécutées en parallèle sur les différents processeurs.
Par ailleurs, nous avons choisi d'expliciter l'expression de la transmission des données entre les
tâches, contrairement à ce qui est habituellement fait.
Grâce à cela, la bibliothèque automatise notamment la transmission de paramètres qui ne doivent pas
être partagés par des tâches parallèles.
Cela nous permet en particulier de garantir la répétabilité des exécutions y compris lorsque, par
exemple, les tâches utilisent des nombres pseudo-aléatoires.
En tenant compte de la politique d'exécution choisie et des nombres de processeurs possibles, nous
réduisons la quantité nécessaire de ces paramètres ne devant pas être partagés.
Ainsi, cette seconde bibliothèque propose elle aussi un cadre de programmation à plusieurs niveaux.
Celle-ci est extensible au niveau de ses politiques d'exécution ou des motifs pour la construction
des squelettes algorithmiques.
On peut l'utiliser pour définir une variété de squelettes algorithmiques, lesquels serviront ensuite
à un développeur pour écrire des programmes dont la parallélisation sera facilitée.
%}}}
\vfill
\noindent\textbf{Mots-clés} :
métaprogrammation template ;
parallélisation assistée ;
parallélisation automatique ;
bibliothèques actives ;
squelettes algorithmiques ;
répétabilité.
\end{abstract}
%}}}
%{{{ abstract "
\begin{otherlanguage}{english}
\begin{abstract}
%{{{
Hardware performance has been increasing through the addition of computing cores rather than through
increasing their frequency since the early 2000s.
This means that parallel programming is no longer optional should you need to make the best use of
the hardware at your disposal.
Yet many programs are still written sequentially: parallel programming introduces numerous
difficulties.
Amongst these, it is notably hard to determine whether a sequence of a program can be executed in
parallel, i.e. preserving its behaviour as well as its overall result.
Even knowing that it is possible to parallelise a piece of code, doing it correctly is another
problem.
In this thesis, we present two approaches to make writing parallel software easier.
We present an active library (using C++ template metaprogramming to operate during the compilation
process) whose purpose is to analyse and parallelise loops.
To do so, it builds a representation of each processed loop using expression templates through an
embedded language.
This allows to know which variables are used and how they are used.
For the case of arrays, which are common within loops, it also acquires the index functions.
The analysis of this information enables the library to identify which instructions in the loop can
be run in parallel.
Interdependent instructions are detected by knowing the variables and their access mode for each
instruction.
Given a group of interdependent instructions and the known index functions, the library decides if
the instructions can be run in parallel or not.
We want this library to help developers writing loops that will be automatically parallelised
whenever possible and run sequentially as without the library otherwise.
Another focus is to provide this to serve as a framework to integrate new methods for parallelising
programs and extended analysis rules.
We introduce another active library that aims to help developers by assisting them in writing
parallel software instead of fully automating it.
This library uses algorithmic skeletons to let the developer describe its algorithms with both its
sequential and parallel parts by assembling atomic execution patterns such as a series of tasks or a
parallel execution of a repeated task.
This description includes the data flow, that is how parameters and function returns are
transmitted.
Usually, this is automatically set by the algorithmic skeleton library, however it gives the
developer greater flexibility and it makes it possible, amongst other things, for our library to
automatically transmit special parameters that must not be shared between parallel tasks.
One feature that this allows is to ensure repeatability from one execution to another even for
stochastic algorithms.
Considering the distribution of tasks on the different cores, we even reduce the number of these
non-shared parameters.
Once again, this library provides a framework at several levels.
Low-level extensions consist of the implementation of new execution patterns to be used to build
skeletons.
Another low-level axis is the integration of new execution policies that decide how tasks are
distributed on the available computing cores.
High-level additions will be libraries using ours to offer ready-to-use algorithmic skeletons for
various fields.
%}}}
\vfill
\noindent\textbf{Keywords}:
template metaprogramming;
assisted parallelisation;
automatic parallelisation;
active libraries;
algorithmic skeletons;
repeatability.
\end{abstract}
\end{otherlanguage}
%}}}

98
src/0_3_preamble.tex Normal file
View File

@ -0,0 +1,98 @@
\chapterx*{Avant-propos}
Ce document présente les travaux effectués durant ma thèse.
Les projets correspondants sont accessibles à l'adresse \url{https://phd.pereda.fr/dev}.
Parmi ces projets se trouvent les deux bibliothèques principales :
\begin{itemize}
\item \url{https://phd.pereda.fr/dev/pfor} qui est présentée dans \acref{ch:pfor} ;
\item \url{https://phd.pereda.fr/dev/alsk} qui est présentée dans \acref{ch:alsk}.
\end{itemize}
De nombreux extraits de code source sont étudiés.
Ceux-ci sont la plupart du temps simplifiés pour se concentrer sur les points intéressants.
En général, le langage de programmation est indiqué et, en particulier pour le C et le C++, le
standard à partir duquel le code est valide.
Ces indications sont
\tikz[remember picture,anchor=base,inner sep=0pt]{\node(tikz preamble anchor 0){en dessous à droite};}
des extraits de code.
\begin{cppcode}
// source code
\end{cppcode}
\mincpp{\tikz[overlay,remember picture]{
\node(tikz preamble anchor 1)[draw=green!30!black!70,very thick,dashed,rectangle,
rounded corners=1mm,minimum width=5.5em,minimum height=4ex, shift={(-1.2em,+.6ex)}]{};
\draw[draw=green!30!black!70,ultra thick,->,>=stealth,opacity=.5]
([shift={(3ex,-.5mm)}]tikz preamble anchor 0.south) to[bend right] ([xshift=-.5mm]tikz preamble anchor 1.west);
}14}
Certaines figures sont dynamiques\footnote{Sauf si le document a été compilé spécifiquement pour
impression.}.
Dans ce cas se trouvera sous la figure concernée le symbole $\triangleright$ suivi d'indications
spécifiques.
\begin{ocg}{Exemples d'OCG}{ocgexamples}{1}
\begin{multicols}{2}
\begin{ocg}{Exemple TikZ}{ocgexample0}{1}
\begin{figure}[H]
\def\prefix{tikz-preamble-ocg-example0}
\centering
\ocgcase{\prefix}{\prefix-off}{on}{%
\begin{tikzpicture}%
\node[common/drawfill=black,circle,minimum size=12mm,show ocg={\prefix-on}]{Test};
\end{tikzpicture}%
}%
\ocgcase{\prefix}{\prefix-on}{off}{%
\begin{tikzpicture}%
\node[common/drawfill=green!35!black,circle,minimum size=12mm]{\checkmark};
\end{tikzpicture}%
}%
\\
{%
\small $\triangleright$ cliquer sur le disque \og Test \fg pour tester%
}%
\end{figure}
\end{ocg}
\columnbreak
\begin{ocg}{Exemple avec liens}{ocgexample1}{1}
\begin{figure}[H]
\def\prefix{tikz-preamble-ocg-example1}
\centering
\ocmdcase{\prefix-0}{%
\begin{tikzpicture}%
\node[common/drawfill=black,rectangle,rounded corners,minimum width=12mm,minimum height=12mm]{\num{0}};
\end{tikzpicture}%
}%
\foreach \i in {1,...,5} {%
\pgfmathtruncatemacro{\ii}{\i*\i}%
\ocmdcase{\prefix-\i}{%
\begin{tikzpicture}%
\node[common/drawfill=black,rectangle,rounded corners,minimum width=12mm,minimum height=12mm]{\num{\ii}};
\end{tikzpicture}%
}%
}%
\\
{%
\small $\triangleright$ afficher le carré de :%
\ocgradio{\prefix}{\prefix-0}{0}{on}%
\foreach \i in {1,...,5} {%
\ocgradio{\prefix}{\prefix-\i}{\i}{off}%
}%
}%
\end{figure}
\end{ocg}
\end{multicols}
\end{ocg}
Si le symbole $\triangleright$ apparaît mais que rien ne se passe lors du clic, le lecteur utilisé
n'est pas compatible.
Les lecteurs testés sont les suivants :
\begin{itemize}
\item Evince ;
\item Okular ;
\item Adobe Acrobat Reader DC.
\end{itemize}
Quoi qu'il arrive, la figure affichée par défaut est celle dont il est question au sein du texte.
Les éléments dynamiques peuvent apporter une aide à la compréhension ou des éléments supplémentaires
mais ne sont en aucun cas indispensables à la lecture.

View File

@ -0,0 +1,33 @@
\chapterx*{Avant-propos}
Ce document présente les travaux effectués durant ma thèse.
Les projets correspondants sont accessibles à l'adresse \url{https://phd.pereda.fr/dev}.
Parmi ces projets se trouvent les deux bibliothèques principales :
\begin{itemize}
\item \url{https://phd.pereda.fr/dev/pfor} qui est présentée dans \acref{ch:pfor} ;
\item \url{https://phd.pereda.fr/dev/alsk} qui est présentée dans \acref{ch:alsk}.
\end{itemize}
De nombreux extraits de code source sont étudiés.
Ceux-ci sont la plupart du temps simplifiés pour se concentrer sur les points intéressants.
En général, le langage de programmation est indiqué et, en particulier pour le C et le C++, le
standard à partir duquel le code est valide.
Ces indications sont
\tikz[remember picture,anchor=base,inner sep=0pt]{\node(tikz preamble anchor 0){en dessous à droite};}
des extraits de code.
\begin{cppcode}
// source code
\end{cppcode}
\mincpp{\tikz[overlay,remember picture]{
\node(tikz preamble anchor 1)[draw=green!30!black!70,very thick,dashed,rectangle,
rounded corners=1mm,minimum width=5.5em,minimum height=4ex, shift={(-1.2em,+.6ex)}]{};
\draw[draw=green!30!black!70,ultra thick,->,>=stealth,opacity=.5]
([shift={(3ex,-.5mm)}]tikz preamble anchor 0.south) to[bend right] ([xshift=-.5mm]tikz preamble anchor 1.west);
}14}
Ce document a été compilé spécifiquement pour impression.
La version numérique comporte des figures dynamiques.
Celles-ci ont toujours un rendu par défaut qui est le seul dont il est question au sein du texte.
Les éléments dynamiques peuvent apporter une aide à la compréhension ou des éléments supplémentaires
mais ne sont en aucun cas indispensables à la lecture.

170
src/0_intro.tex Normal file
View File

@ -0,0 +1,170 @@
\chapterx*{Introduction}
%{{{ introduction "
L'informatique est employée dans de nombreux domaines, que ce soit comme sujet d'étude ou comme
outil d'application, notamment en sciences, y compris dans le cadre de démonstrations mathématiques.
L'évolution de celle-ci repose de manière croissante sur la puissance de calcul qu'apportent les
ordinateurs.
Or, jusqu'au début des années \num{2000}, la puissance d'un ordinateur dépendait sensiblement de la
fréquence du processeur qui effectue les calculs, laquelle a été confrontée à une limite physique au
delà de laquelle les fuites de courants étaient trop importantes, allant même jusqu'à faire fondre
les processeurs~\autocite{ref:namsungkim2003}.
Depuis, pour outrepasser cette limite, les processeurs se voient pourvus de cœurs de calcul de plus
en plus nombreux.
En dehors de notions élémentaires, l'utilisation de l'informatique comme outil pour résoudre des
problèmes nécessite surtout des connaissances dans le domaine du problème.
Mais l'utilisation concurrente de plusieurs cœurs de calcul introduit des difficultés propres à
l'informatique et donc une expertise que n'ont pas la plupart de ses utilisateurs, et qu'ils ne
devraient pas être contraints à maîtriser.
Ces difficultés couvrent des sujets variés, incluant la connaissance du fonctionnement du matériel,
de son interaction avec les couches logicielles les plus basses jusqu'à des principes propres à la
programmation concurrente : gestion de la communication entre les éléments concurrents ;
synchronisation du travail effectué ; implémentation de patrons de conception dédiés ; maintien de
la répétabilité malgré l'utilisation de nombres pseudo-aléatoires ; ...
Du fait de ces difficultés, une majorité de programmes sont développés sans bénéficier réellement du
potentiel du matériel à disposition.
%}}}
%{{{ solutions existantes "
Des solutions ont naturellement été étudiées, permettant d'abord l'utilisation des mêmes primitives
indépendamment de l'architecture exacte sur laquelle est déployé le programme, là où initialement
chaque système fournissait sa propre interface.
Des instructions spécifiques, mettant en place tout ce qui est nécessaire à la parallélisation, ont
été introduites, notamment au sein de certains compilateurs.
Cette technique va jusqu'à la création de langages dédiés exposant des syntaxes adaptées.
Tout cela a participé à assister les développeurs dans l'écriture de programmes s'exécutant en
parallèle sur plusieurs processeurs.
Il s'est également agit de proposer l'automatisation de la génération de programmes parallèles à
partir de leur écriture séquentielle classique.
Différentes techniques existent, dont, à nouveau, l'exploitation des compilateurs pour analyser le
code source et produire, sans que soit nécessaire une intervention de la part du développeur, un
programme dont l'exécution est
parallèle~\autocite{ref:zima1988,ref:blume1995,ref:ahmad1997,ref:fonseca2016}.
Similairement, des langages de programmation se sont mis à intégrer des constructions orientées vers
la parallélisation et plus ou moins transparentes pour le
développeur~\autocite{ref:roscoe1988,ref:loveman1993,ref:chamberlain2007,ref:rieger2019}.
Autant pour l'implémentation d'outils simplifiant la mise en œuvre de la parallélisation que pour
son automatisation, de nombreuses bibliothèques logicielles sont disponibles et permettent
d'apporter à l'existant -- langages et compilateurs -- de nouvelles
possibilités~\autocite{ref:kuchen2002a,ref:chan2004,ref:falcou2008,ref:videau2018,ref:ernstsson2018}.
Il est même possible, dans certains cas, qu'une bibliothèque logicielle (indépendante du
compilateur) agisse pratiquement aussi profondément que le peut une extension de compilateur (à
l'inverse, spécifique à celui-ci).
Ces bibliothèques, dites actives~\autocite{ref:veldhuizen1998a}, ouvrent la voie à de nouvelles
façons de proposer des outils aux développeurs.
%}}}
%{{{ besoin "
Les solutions aux problèmes de la parallélisation basées sur des bibliothèques logicielles actives
sont relativement peu nombreuses par rapport aux autres approches.
Elles sont plus contraignantes à produire qu'une extension de compilateur.
Il est notamment difficile d'en écrire une qui ne cause effectivement pas un surcoût rédhibitoire en
temps d'exécution, en mémoire utilisée ou encore en temps de compilation, comparé à une
implémentation manuelle ou à ce qui se fait au niveau des compilateurs.
Selon le cadre dans lequel la bibliothèque active est conçue, il peut également être
particulièrement difficile de déboguer.
Le C++ et ses templates, supportant une forme de métaprogrammation à la base de ce qui permet
l'élaboration de bibliothèques actives au sein de ce langage, est connu pour cela.
En partie parce que ces templates n'ont pas été pensé initialement pour cela (ils servent avant tout
à la dimension générique du langage), il est difficile de suivre ce qu'ils causent durant la
compilation.
Malgré cette limite, qui tend par ailleurs à s'amoindrir avec les évolutions du langage, cette forme
de métaprogrammation est très employée car elle permet, lorsqu'elle est bien utilisée, d'atteindre
des résultats très proches de ce que l'on obtiendrait en écrivant soi-même le code produit.
%}}}
%{{{ proposition "
Avec cette thèse, nous voulons proposer principalement deux nouveaux outils.
Le premier est un outil de parallélisation automatique, avec une bibliothèque active détectant
elle-même ce qui peut validement être exécuté en parallèle et qui génère le code correspondant.
La détection doit reposer sur des mécanismes extensibles afin de permettre l'ajout de nouvelles
règles, déterminant, à partir des accès aux variables, ce qu'il est possible de faire de chaque
instruction.
Similairement, la génération de code doit avoir la capacité de fonctionner indépendamment de la
méthode de parallélisation sous-jacente.
Pour cela, il faut donc voir la manière de paralléliser comme une simple configuration qui peut être
indiquée à la bibliothèque plutôt que comme un élément qui lui est central.
À l'inverse, le fonctionnement de la bibliothèque ne doit pas affecter négativement et
significativement la performance du programme par rapport à ce qui peut être accompli similairement
\og à la main \fg.
Cela doit en particulier être vrai lorsque son utilisation aboutit à un programme non parallélisé.
Le second outil relève, quant à lui, de la parallélisation assistée, laquelle laisse au moins à la
charge du développeur le soin de décider quelles sections du programme doivent être parallélisées
avec une deuxième bibliothèque, également active.
Pour ceci, nous proposons une nouvelle vision des squelettes algorithmiques avec davantage de
contrôle de la part du développeur.
Un but est de permettre une utilisation très flexible des squelettes algorithmiques, par exemple sur
l'aspect de la transmission des paramètres entre les différentes parties d'un squelette.
Un autre but est l'analyse des squelettes algorithmiques durant la compilation pour en déduire, par
exemple, des optimisations possibles de celui-ci ou des schémas efficaces d'exécution parallèle.
La parallélisation, particulièrement lorsqu'elle est combinée à l'utilisation de nombres
pseudo-aléatoires, rend plus difficile la préservation de la répétabilité d'un programme,
c'est-à-dire la capacité à exécuter plusieurs fois ce programme dans les mêmes conditions en
obtenant systématiquement le même résultat.
Sans répétabilité, la capacité de débogage est illusoire, et pour cette raison, nous voulons
proposer un moyen de garantir cette répétabilité grâce aux squelettes algorithmiques.
%}}}
%{{{ structure "
Ce document présente les travaux effectués au titre de cette thèse.
Pour ce faire, il est scindé en \num{5} chapitres.
\Acref{ch:par} traite de la parallélisation en partant des notions élémentaires matérielles et
logicielles et en allant jusqu'aux concepts spécifiques de plus haut niveau qui permettent de
résoudre des problèmes introduits par cette discipline.
Ce chapitre détaille ensuite différentes couches supérieures qui simplifient l'application de la
parallélisation, principalement sur les deux aspects que sont la parallélisation automatique et la
parallélisation assistée.
\Acref{ch:gnx} introduit un paradigme de programmation nommé \og généricité \fg, en particulier en
C++.
En effet, la généricité de ce langage fonctionnant durant la compilation grâce, entre autres, aux
templates, est à la base de la métaprogrammation template.
Y sont donc traitées la création et l'utilisation de templates, ainsi que leur spécialisation et les
contraintes de sélection associées à celles-ci.
Ce chapitre aborde aussi des problématiques spécifiques à cette généricité et les solutions qui y
répondent.
\Acref{ch:mp} détaille ensuite la métaprogrammation, notamment celle du C++ avec la notion de
template, les travaux effectués pour cette thèse reposant tous sur cette possibilité du langage C++.
En utilisant les notions de généricité du chapitre précédent, il présente l'implémentation de
différents algorithmes dont le déroulement s'effectue durant la compilation du programme.
Il progresse sur ce sujet jusqu'aux patrons d'expression, une technique en métaprogrammation
template permettant la représentation et l'utilisation, durant la compilation, de portions de code
source.
\Acref{ch:pfor} présente une bibliothèque active en C++ et utilisant la métaprogrammation template
pour la parallélisation automatique de boucles.
Cette proposition est expliquée en commençant par le détail des tests effectués par la bibliothèque
pour déterminer l'indépendance d'instructions et leur capacité à être exécutées en parallèle sans
créer de conflit d'accès aux données.
Ensuite, le chapitre traite de la détection des données nécessaires à l'application de ces tests,
qui repose sur des patrons d'expression, et qui permet d'acquérir une vision fine des instructions.
La génération du code éventuellement parallèle, incluant donc l'exécution des tests durant la
compilation, est subséquemment exposée.
Pour finir, l'efficacité de l'utilisation de cette bibliothèque est évaluée au moyen de mesures de
temps de compilation et d'exécution.
Cela est effectué pour différents cas et pour des codes utilisant la bibliothèque par rapport à des
codes équivalents ne l'utilisant pas.
Le \hyperref[ch:alsk]{cinquième et dernier chapitre} présente une seconde bibliothèque active,
utilisant également la métaprogrammation template du C++, cette fois-ci orientée vers la
parallélisation assistée au moyen de squelettes algorithmiques.
Ce chapitre introduit un problème de \gls{RO} et des métaheuristiques utilisées pour le résoudre.
Ces métaheuristiques sont ensuite utilisées pour illustrer les concepts généraux de squelettes
algorithmiques ainsi que ceux propres à notre proposition.
Le chapitre se poursuit avec l'étude de la répartition des tâches à exécuter en parallèle en
fonction du squelette algorithmique et de sa structure.
Le problème de la répétabilité des exécutions parallèles en tenant compte, notamment, de
l'utilisation de nombres pseudo-aléatoires est ensuite traité.
Après les premières sections qui présentent chacune un aspect de la conception des squelettes
algorithmiques en utilisant cette bibliothèque, ce chapitre montre comment ceux-ci peuvent être
utilisés en pratique, notamment par le biais d'une syntaxe alternative définissant un langage
embarqué au sein du C++.
Cette bibliothèque est testée pour résoudre des instances de \gls{TSP} et est comparée en temps
d'exécution avec des implémentations équivalentes ne l'utilisant pas.
%}}}

8
src/1_par.tex Normal file
View File

@ -0,0 +1,8 @@
\chapterx{par}{Parallélisation automatique et assistée}
\inputsrc{0_intro}
\inputsrc{1_general}
\inputsrc{2_comp}
\inputsrc{3_auto}
\inputsrc{4_assist}
\inputsrc{5_conclusion}

17
src/2_gnx.tex Normal file
View File

@ -0,0 +1,17 @@
\chapterx{gnx}{Généricité en C++}
\inputsrc{0_intro}
\inputsrc{1_cpp}
\inputsrc{2_ccpp}
\inputsrc{3_syntax}
\inputsrc{4_tad}
\inputsrc{5_spe}
\inputsrc{6_sfinae}
\inputsrc{7_concepts}
\inputsrc{8_inst}
\inputsrc{9_overload}
\inputsrc{a_auto}
\inputsrc{b_forward}
\inputsrc{c_pack}
\inputsrc{d_ifcx}
\inputsrc{e_conclusion}

6
src/3_mp.tex Normal file
View File

@ -0,0 +1,6 @@
\chapterx{mp}{Métaprogrammation template}
\inputsrc{0_intro}
\inputsrc{1_types}
\inputsrc{2_mpt}
\inputsrc{3_conclusion}

9
src/4_pfor.tex Normal file
View File

@ -0,0 +1,9 @@
\chapterx{pfor}{Analyse et parallélisation automatique de boucles}
\inputsrc{0_intro}
\inputsrc{1_relwork}
\inputsrc{2_conditions}
\inputsrc{3_detection}
\inputsrc{4_generation}
\inputsrc{5_results}
\inputsrc{6_conclusion}

11
src/5_alsk.tex Normal file
View File

@ -0,0 +1,11 @@
\chapterx{alsk}{Parallélisation et répétabilité par les squelettes algorithmiques}
\inputsrc{0_intro}
\inputsrc{1_relwork}
\inputsrc{2_app}
\inputsrc{3_concept}
\inputsrc{4_exec}
\inputsrc{5_repeat}
\inputsrc{6_usage}
\inputsrc{7_results}
\inputsrc{8_conclusion}

219
src/6_conclusion.tex Normal file
View File

@ -0,0 +1,219 @@
\chapterx*{Conclusion}
%{{{ introduction "
La programmation parallèle induit de nombreuses difficultés par rapport à la programmation
séquentielle classique.
En premier lieu, il faut identifier les portions de code source qui peuvent être exécutées en
parallèle sans invalider leur comportement, c'est-à-dire en conservant le même déroulement lors de
leur exécution jusqu'à l'obtention du même résultat.
Lorsque ces portions de code sont identifiées, la mise en place d'une stratégie de parallélisation
demande un nouvel effort.
De plus, une évolution du programme, même mineure, peut nécessiter une maintenance coûteuse
puisqu'il faut évaluer à nouveau s'il est possible de l'exécuter en parallèle et mettre à jour le
code en conséquence.
Un programme efficacement parallélisé, s'il utilise par exemple des nombres pseudo-aléatoires, peut
ne plus être répétable d'une exécution à l'autre.
D'autre part, en matière de vérification, il est précieux de pouvoir tester les résultats d'un
programme parallèle par rapport à son équivalent exécuté séquentiellement.
Cette thèse présente des outils conçus pour répondre à différents problèmes introduits par la
programmation parallèle, dont la répétabilité de codes stochastiques faisant usage de nombres
pseudo-aléatoires.
Afin de pouvoir expliquer la théorie et le fonctionnement des propositions faites dans cette thèse,
celle-ci explique les rudiments de la programmation parallèle ainsi que les différentes abstractions
et automatisations qui existent.
Deux chapitres sont ensuite dédiés à la généricité, notamment en C++, et à la métaprogrammation,
notamment celle dite template du C++.
Ils détaillent en particulier les fonctionnalités permises par ces paradigmes et qui servent ensuite
dans l'implémentation des bibliothèques actives que sont les outils proposés.
%}}}
%{{{ pfor "
La première bibliothèque, présentée dans \acref{ch:pfor}, propose une interface pour automatiser la
parallélisation d'instructions au sein d'une boucle.
Celle-ci emploie les patrons d'expressions pour acquérir une représentation des instructions de la
boucle sous la forme d'un \gls{ASA}.
Cet \gls{ASA} détaille jusqu'aux opérations sur les indices d'accès aux tableaux -- appelées
fonctions d'indice -- dont les opérandes sont alors connus dès la compilation.
Grâce à cela, la bibliothèque vérifie si tous les accès aux données permettent une exécution
parallèle, et ce selon les caractéristiques des fonctions d'indice :
\begin{enumerate}
\item si toutes sont affines, la bibliothèque répond, avec une spécificité et une sensibilité
parfaite, en calculant l'existence de solutions à un ensemble d'équations diophantiennes ;
\item sinon, si toutes sont injectives, elle répond, avec une sensibilité imparfaite, en utilisant
un test simplifié ;
\item sinon la bibliothèque suppose par défaut que les instructions ne peuvent être parallélisées.
\end{enumerate}
Cette séquence de tests garantit une spécificité parfaite et empêche donc la parallélisation d'un
code qui ne peut l'être sainement.
Ces tests sont appliqués à des groupes d'instructions qui sont formés de sorte que deux instructions
quelconques venant de groupes différents sont indépendantes quant aux données qu'elles utilisent.
De cette manière, les instructions qui ne doivent être exécutées en parallèle peuvent être
maintenues séparées de celles pouvant être exécutées en parallèle si elles sont indépendantes.
Ainsi, la non-parallélisabilité de ces premières instructions ne gêne pas la parallélisabilité des
dernières, alors qu'une analyse sur l'ensemble complet aurait logiquement rapporté une réponse
négative quant à la possibilité d'exécuter les instructions en parallèle.
À partir des fonctions d'indices seules, la bibliothèque détermine automatiquement si celles-ci sont
affines.
Quant aux autres propriétés telles que l'injectivité, elles peuvent être indiquées voire déduites.
C'est par exemple le cas de l'injectivité si la fonction d'indice est strictement croissante ou
strictement décroissante.
Une fois que les ensembles d'instructions parallélisables et non parallélisables sont ainsi définis,
la bibliothèque reproduit le code représenté par l'\gls{ASA} en intégrant ce qui est nécessaire pour
que soient exécutées en parallèles les instructions concernées.
Pour cela, il est possible d'utiliser différents générateurs.
La bibliothèque propose une parallélisation avec OpenMP ou en utilisant des \en{threads} \gls{POSIX}
ou encore une génération des instructions en utilisant la technique du déroulement de boucle.
La bibliothèque a été testée en comparant ses performances avec des programmes équivalents écrits
dans les meilleures règles de l'art, de façon \og artisanale \fg (et donc sans l'utiliser).
Les temps de compilation, bien qu'il y ait certainement encore des améliorations possibles sur cet
aspect, sont raisonnables et permettent une utilisation au sein de projets.
Les temps d'exécution montrent que l'abstraction apportée par la bibliothèque n'implique que des
surcoûts minimes.
%}}}
%{{{ alsk "
La seconde proposition faite dans cette thèse est une autre bibliothèque active ayant pour objectif
la parallélisation assistée par les squelettes algorithmiques.
Contrairement à la première proposition, les portions du programme qui peuvent être exécutées en
parallèle sont intrinsèquement liées au squelette algorithmique que définit le développeur.
Cette bibliothèque dispose de sa propre manière de concevoir des squelettes algorithmiques.
Celle-ci repose sur une séparation initiale de deux concepts : la structure et les liens.
La structure du squelette est une composition d'autres structures et d'os, les éléments structurels
atomiques introduits dans cette thèse.
Chaque os correspond à un motif d'exécution, par exemple l'exécution séquentielle de plusieurs
tâches ou l'exécution parallèle d'une même tâche répétée, suivie d'une autre pour sélectionner le
meilleur résultat produit.
Quant aux liens, il s'agit d'une description des transferts de données entre les différentes tâches
à exécuter.
Cela se définit en utilisant des paramètres spéciaux, lesquels indiquent à la bibliothèque ce par
quoi ils doivent être remplacés (un paramètre de la tâche appelante, la valeur de retour d'une
autre, ...).
La structure du squelette permet de savoir quels éléments peuvent être exécutés en parallèle.
Il est donc par exemple possible de déterminer le nombre de niveaux de parallélisation dont on
dispose pour un squelette donné.
Pour cela, une bibliothèque annexe d'outils pour la métaprogrammation template est utilisée.
Celle-ci comporte des algorithmes pour parcourir des listes et des arbres de types, et un squelette
algorithmique peut être transformé en arbre (et un arbre en liste si nécessaire).
En utilisant, notamment, l'information du nombre de niveaux parallélisables, la bibliothèque permet
l'optimisation de la répartition des tâches sur les différents \en{threads} selon plusieurs
politiques d'exécution : \en{thread pool} ; répartition équilibrée ; répartition équilibrée
n'utilisant que le premier niveau ; ...
Dans le cas du \en{thread pool}, l'équilibrage de la charge entre les différents \en{threads} est
automatique et dynamique, au coût d'une synchronisation à effectuer pour l'accès aux tâches à
exécuter.
La répartition équilibrée proposée suppose une durée similaire dans l'exécution des différentes
tâches et les distribue aux \en{threads} de manière à ce que chacun en ait, à une près, le même
nombre.
Cette hypothèse semble raisonnable en particulier pour des os répétant une même tâche plusieurs
fois, os fréquemment utilisés dans l'implémentation de métaheuristiques.
Grâce aux connaissances apportées par le choix d'une politique d'exécution et au contrôle permis par
les liens, cette thèse propose une solution au problème de la perte de répétabilité lors de
l'exécution parallèle d'un programme utilisant des nombres pseudo-aléatoires.
Cette solution permet plus généralement le maintien de la répétabilité lors de l'utilisation de
données qui doivent n'être utilisées que par un unique \en{thread}.
Ces données doivent donc être associées aux tâches qui partagent le \en{thread} qui les exécute.
Une solution triviale consiste à fournir à chaque tâche ses propres données.
Lorsqu'il s'agit de nombres pseudo-aléatoires, cela signifie qu'il faut déterminer une séquence
indépendante pour chaque tâche, ce qui devient coûteux, entre autres en mémoire, lorsque le nombre
de tâches augmente et que le statut initial du générateur est conséquent (proche de
\SI{2.5}{\kibi\octet} par exemple pour un état initial de Mersenne Twister MT19937).
En tenant compte de la distribution des tâches sur les \en{threads}, et ce pour différentes
quantités de \en{threads}, on détermine quelles tâches sont toujours exécutées par un \en{thread}
commun (indépendamment du nombre de \en{threads}) et on leur associe une séquence de nombres
pseudo-aléatoires commune (état initial partagé).
Ceci permet de garantir la répétabilité non seulement d'une exécution à l'autre, mais également
lorsque le nombre de \en{threads} varie.
Les tâches ayant besoin de nombres pseudo-aléatoires peuvent alors en recevoir un automatiquement au
moyen d'un paramètre spécial que les liens permettent d'utiliser.
Cette bibliothèque a été utilisée pour résoudre des instances de \gls{TSP} par la métaheuristique
\graspels{}.
Les temps d'exécution sont comparés à ceux obtenus avec une implémentation manuelle d'un \graspels{}
et correspondent à des exécutions parallèles et à des exécutions séquentielles (afin de valider que,
même dans ce cas, la bibliothèque n'induit pas de surcoût).
Dans tous les cas, les temps obtenus sont analogues.
%}}}
\chapterx*{Limites et perspectives}
%{{{ perspectives "
%{{{ pfor "
Nous avons bien conscience que notre première proposition, orientée sur l'analyse et la
parallélisation automatique de boucles, est incomplète dans la mesure où de multiples boucles
imbriquées ne sont pas détectées.
Pour le moment, l'utilisation de multiples boucles imbriquées signifie qu'une seule sera traitée
pour la parallélisation.
Ce fait n'est pas gênant si l'on considère les architectures matérielles multi-cœurs actuelles.
La bibliothèque ne propose pas d'outil pour mettre en place une parallélisation automatique sur
plusieurs niveaux car elle ne permet pas, à ce stade, l'utilisation de l'indice d'un niveau à un
niveau inférieur.
C'est une piste de recherche que nous envisageons d'explorer à l'avenir.
De plus, il sera intéressant de tester différentes méthodes pour appliquer la parallélisation.
Notamment l'utilisation d'un \en{thread pool} afin de voir si l'introduction de sections critiques
induit un coût négligeable par rapport au gain supposé apporté par le fait d'éviter la recréation de
\en{threads} à chaque nouvelle boucle parallélisée.
Au niveau de l'analyse et particulièrement des tests permettant de déterminer la parallélisabilité
des instructions, l'implémentation du modèle polyédral est également une piste.
Celle-ci peut notamment être utile au support de multiples boucles imbriquées, mais peut
éventuellement également servir à augmenter la sensibilité du test pour des boucles à un seul niveau
lorsque les fonctions d'indice ne sont ni affines ni injectives.
Une autre piste d'amélioration consiste en l'application de transformations sur le code source
traité.
Actuellement, celui-ci est analysé pour sa parallélisabilité, et s'il ne passe pas le test, il est
exécuté séquentiellement.
Il est possible, en modifiant astucieusement les instructions, de conserver le comportement du
programme en éliminant des dépendances, augmentant donc potentiellement sa parallélisabilité.
%}}}
%{{{ alsk "
Par rapport à la seconde proposition, axée sur la parallélisation assistée par l'utilisation de
squelettes algorithmiques, le travail effectué était principalement centré sur l'abstraction
apportée par la bibliothèque de squelettes algorithmiques, au détriment de l'optimisation des
différentes politiques d'exécution fournies.
Ces politiques d'exécution ne sont donc pas implémentées de manière optimale.
Une personne dont la parallélisation est le domaine d'expertise pourrait améliorer cela.
En outre, il serait très intéressant de tester la piste évoquée dans
\acref{subsubsec:alsk/exec/impl/static}, à savoir la préparation, dès la compilation, de la séquence
complète des tâches qu'exécutera chaque \en{thread} afin d'éviter un défaut actuel de la politique
d'exécution répartissant les tâches de manière équilibrée : les \en{threads} des niveaux parallèles,
à l'exception du tout premier, sont créés de multiples fois.
Une autre idée est de permettre l'ajustement du poids des tâches à exécuter afin de ne plus supposer
un temps d'exécution homogène.
En utilisant ces poids, une distribution équilibrée des tâches sur les différents \en{threads} peut,
peut-être, être aussi efficace en termes d'équilibrage de charge que ce que peut accomplir
dynamiquement un \en{thread pool}.
De plus, une exécution, partielle ou sur des données réduites, du programme permettrait
éventuellement la génération automatique de ces poids.
Les os proposés au sein de cette thèse répondent aux besoins levés par l'applicatif en \gls{RO} que
nous avons utilisé.
Néanmoins, des motifs tels que le \en{pipeline}, sont manquants pour une utilisation réellement
générale.
Certains de ces nouveaux os pourraient nécessiter l'ajout de primitives au sein des exécuteurs.
Le système de liens apporte des avantages intéressants, mais également une syntaxe plus lourde pour
le développeur qui, en utilisant d'autres bibliothèques de squelettes algorithmiques, pourrait
préférer une valeur par défaut.
L'\gls{EDSL} introduit en partie cette possibilité de valeur par défaut et même de déduction de
liens, mais cela manque aux couches plus basses.
Ce système de liens permet par ailleurs de connaître exactement quelles tâches nécessitent
l'utilisation, par exemple, de nombres pseudo-aléatoires.
En utilisant cette information, il est possible de réduire encore le nombre de \gls{PRNG} devant
être créés pour garantir la répétabilité du programme.
%}}}
%}}}

80
src/acronyms.tex Normal file
View File

@ -0,0 +1,80 @@
\newacronym{ABI}{ABI}{\en{Application Binary Interface}}
\newacronym{API}{API}{\en{Application Programming Interface}}
\newacronym{ASA}{ASA}{arbre syntaxique abstrait}
\newacronym{AST}{AST}{\en{Abstract Syntax Tree}}
\newacronym{AVX}{AVX}{\en{Advanced Vector eXtensions}}
\newacronym{BLP}{BLP}{\en{Bit-Level Parallelism}}
\newacronym{C++ AMP}{C++ AMP}{C++ {\en{Accelerated Massive Parallelism}}}
\newacronym{CPP}{CPP}{\en{C PreProcessor}}
\newacronym{CPU}{CPU}{\en{Central Processing Unit}}
\newacronym{CTAD}{CTAD}{\en{Class Template Argument Deduction}}
\newacronym{CUDA}{CUDA}{\en{Compute Unified Device Architecture}}
\newacronym{DSL}{DSL}{\en{Domain Specific Language}}
\newacronym{DSP}{DSP}{\en{Digital Signal Processor}}
\newacronym{EBO}{EBO}{\en{Empty Base Optimization}}
\newacronym{EDSL}{EDSL}{\en{Embedded Domain Specific Language}}
\newacronym{ELS}{ELS}{\en{Evolutionary Local Search}}
\newacronym{ET}{ET}{\en{Expression Templates}}
\newacronym{FIFO}{FIFO}{\en{First In, First Out}}
\newacronym{FPGA}{FPGA}{\en{Field-Programmable Gate Array}}
\newacronym{GPGPU}{GPGPU}{\en{General-Purpose computing on \gls{GPU}}}
\newacronym{GPPL}{GPPL}{\en{General Purpose Programming Language}}
\newacronym{GPU}{GPU}{\en{Graphics Processing Unit}}
\newacronym{GRASP}{GRASP}{\en{Greedy Randomized Adaptive Search Procedure}}
\newacronym{HPC}{HPC}{\en{High Performance Computing}}
\newacronym{ID}{ID}{\en{Instruction Decode}}
\newacronym{IEEE}{IEEE}{\en{Institute of Electrical and Electronics Engineers}}
\newacronym{IF}{IF}{\en{Instruction Fetch}}
\newacronym{ILP}{ILP}{\en{Instruction-Level Parallelism}}
\newacronym{ILS}{ILS}{\en{Iterative Local Search}}
\newacronym{IPC}{IPC}{\en{Inter-Process Communication}}
\newacronym{MA}{MA}{\en{Memory Access}}
\newacronym{MIMD}{MIMD}{\en{Multiple-Instruction stream -- Multiple-Data stream}}
\newacronym{MISD}{MISD}{\en{Multiple-Instruction stream -- Single-Data stream}}
\newacronym{MMX}{MMX}{\en{multimedia extensions}}
\newacronym{MPI}{MPI}{\en{Message Passing Interface}}
\newacronym{MPT}{MPT}{métaprogrammation template}
\newacronym{NUMA}{NUMA}{\en{Non-Uniform Memory Access}}
\newacronym{OpenCL}{OpenCL}{\en{Open Computing Language}}
\newacronym{OpenMP}{OpenMP}{\en{Open Multi-Processing}}
\newacronym{PEPS}{PEPS}{Premier Entré, Premier Sorti}
\newacronym{PGCD}{PGCD}{plus grand commun diviseur}
\newacronym{POO}{POO}{Programmation Orientée Objet}
\newacronym{POSIX}{POSIX}{\en{Portable Operating System Interface}}
\newacronym{PRNG}{PRNG}{\en{Pseudorandom Number Generator}}
\newacronym{RAII}{RAII}{\en{Resource Acquisition Is Initialisation}}
\newacronym{RAW}{RAW}{\en{Read After Write}}
\newacronym{RO}{RO}{Recherche Opérationnelle}
\newacronym{RRID}{RRID}{\en{Resource Release Is Destruction}}
\newacronym{SFINAE}{SFINAE}{\en{Substitution Failure Is Not An Error}}
\newacronym{SIMD}{SIMD}{\en{Single-Instruction stream -- Multiple-Data stream}}
\newacronym{SISD}{SISD}{\en{Single-Instruction stream -- Single-Data stream}}
\newacronym{SMP}{SMP}{\en{Symmetric MultiProcessing}}
\newacronym{SSE}{SSE}{\en{Streaming \gls{SIMD} Extension}}
\newacronym{TAD}{TAD}{\en{Template Argument Deduction}}
\newacronym{TBB}{TBB}{\en{Threading Building Blocks}}
\newacronym{TMP}{TMP}{\en{template metaprogramming}}
\newacronym{TSP}{TSP}{\en{Travelling Salesman Problem}}
\newacronym{UAL}{UAL}{Unité Arithmétique et Logique}
\newacronym{UMA}{UMA}{\en{Uniform Memory Access}}
\newacronym{UVF}{UVF}{Unité de calcul en Virgule Flottante}
\newacronym{WAR}{WAR}{\en{Write After Read}}
\newacronym{WAW}{WAW}{\en{Write After Write}}
\newacronym{WB}{WB}{\en{WriteBack}}

20
src/alg/alsk/app/els.tex Normal file
View File

@ -0,0 +1,20 @@
\begin{algorithmic}
\Function{ELS}{$P, S, N, M$}
\State $S \gets \Call{rechercheLocaleInitiale}{P, S}$
\State $S^* \gets S$
\For{$i = 1..N$}
\For{$j = 1..M$}
\State $S_j \gets \Call{mutation}{S}$
\State $S_j \gets \Call{rechercheLocale}{P, S_j}$
\EndFor
\State $S' \gets \Call{sélection1}{\{S_1, S_2, \dots, S_{M}\}}$
\State $S^* \gets \Call{sélection2}{S^*, S'}$
\If{\Call{critèreAcceptation}{$S'$}}
\State $S \gets S'$
\EndIf
\EndFor
\State \Return $S^*$
\EndFunction
\end{algorithmic}

View File

@ -0,0 +1,10 @@
\begin{algorithmic}
\Function{GRASP}{$N, P$}
\For{$i = 1..N$}
\State $S_i \gets \Call{heuristiqueConstructive}{P}$
\State $S_i \gets \Call{rechercheLocale}{P, S_i}$
\EndFor
\State $S^* \gets \Call{sélection}{\{S_1, S_2, \dots, S_{N}\}}$
\State \Return $S^*$
\EndFunction
\end{algorithmic}

View File

@ -0,0 +1,12 @@
\begin{algorithmic}
\Function{glouton}{$P$}
\State $S \gets \emptyset$
\State Construire la liste $L$ des éléments insérables dans $S$ à partir de $P$
\While{$L \neq \emptyset$}
\State Évaluer le coût d'insertion de chaque élément de $L$
\State Retirer de $L$ l'élément de coût minimal
\State Insérer cet élément dans $S$
\EndWhile
\State \Return $S$
\EndFunction
\end{algorithmic}

17
src/alg/alsk/app/ils.tex Normal file
View File

@ -0,0 +1,17 @@
\begin{algorithmic}
\Function{ILS}{$P, S, N$}
\State $S \gets \Call{rechercheLocaleInitiale}{P, S}$
\State $S^{*} \gets S$
\For{$i = 1..N$}
\State $S' \gets \Call{mutation}{S}$
\State $S' \gets \Call{rechercheLocale}{P, S'}$
\State $S^{*} \gets \Call{sélection}{S^{*}, S'}$
\If{\Call{critèreAcceptation}{$S'$}}
\State $S \gets S'$
\EndIf
\EndFor
\State \Return $S^{*}$
\EndFunction
\end{algorithmic}

View File

@ -0,0 +1,16 @@
\begin{algorithmic}[1]
\Procedure{threadPrincipal}{}
\State $promesse\_type\;promesse$
\State $future\_type\;future$
\State $thread\_type\;thread(\textsc{travail}, promesse)$
\State \Comment{travail précédant la barrière de synchronisation}
\State $\Call{obtenirResultat}{future}$ \Comment{la valeur retournée n'importe pas}
\State \Comment{travail réalisé après la barrière de synchronisation}
\EndProcedure
\Procedure{travail}{$promesse$}
\State \Comment{travail précédant la barrière de synchronisation}
\State $promesse \gets \epsilon$ \Comment{la valeur donnée n'importe pas}
\State \Comment{travail réalisé après la barrière de synchronisation}
\EndProcedure
\end{algorithmic}

View File

@ -0,0 +1,16 @@
\begin{algorithmic}[1]
\Procedure{threadPrincipal}{}
\State $promesse\_type\;promesse$ \Comment{\og promesse \fg{} qui sera donnée au second \en{thread}}
\State $future\_type\;future \gets \Call{obtenirFuture}{promesse}$ \Comment{construction de la
\og future \fg{} correspondante}
\State $thread\_type\;thread(\textsc{travail}, promesse)$
\State \Comment{le \en{thread} principal peut travailler indépendament ici}
\State $resultat \gets \Call{obtenirResultat}{future}$ \Comment{synchronisation entre les deux
\en{threads}}
\EndProcedure
\Procedure{travail}{$promesse$}
\State \Comment{production d'un résultat}
\State $promesse \gets resultat$
\EndProcedure
\end{algorithmic}

View File

@ -0,0 +1,15 @@
\begin{algorithmic}[1]
\State $m \gets PTHREAD\_MUTEX\_INITIALIZER$
\Procedure{threadPrincipal}{}
\State $thread\_id \gets \Call{pthread\_create}{\textsc{travail}}$
\State $\Call{travail}{}$ \Comment{cet appel est exécuté dans le \en{thread} principal}
\State $\Call{pthread\_join}{thread\_id}$
\EndProcedure
\Procedure{travail}{}
\State $\Call{pthread\_mutex\_lock}{m}$
\State ... \Comment{modification d'une variable partagée}
\State $\Call{pthread\_mutex\_unlock}{m}$
\EndProcedure
\end{algorithmic}

View File

@ -0,0 +1,13 @@
\begin{algorithmic}[1]
\State $mutex\_type\;m$ \Comment{le constructeur initialise correctement}
\Procedure{threadPrincipal}{}
\State $thread\_type\;thread(\textsc{travail})$
\Comment{le constructeur appelle \texttt{pthread\_create}}
\State $\Call{travail}{}$
\EndProcedure \Comment{le destructeur de $thread\_type$ appelle \texttt{ptread\_join}}
\Procedure{travail}{}
\State $mutex\_locker\_type\;lock(m)$ \Comment{le constructeur prend le \en{mutex}...}
\State \Comment{modification d'une variable partagée}
\EndProcedure \Comment{... et le destructeur le libère}
\end{algorithmic}

9
src/alg/par/auto/ast.tex Normal file
View File

@ -0,0 +1,9 @@
\begin{algorithmic}[1]
\State $i = 10$
\State $r = 0$
\While{$i \neq 0$}
\State $r = r+i$
\State $i = i-1$
\EndWhile
\State \Return $r$
\end{algorithmic}

View File

@ -0,0 +1,5 @@
\begin{algorithmic}[1]
\If{$R_1 = A$}
\State $R_2 \gets B$ \Comment{L'exécution de cette instruction dépend de l'instruction $R_1 = A$}
\EndIf
\end{algorithmic}

View File

@ -0,0 +1,4 @@
\begin{algorithmic}[1]
\State $R_1 \gets A$ \Comment{Une ressource utilisée en écriture...}
\State $R_2 \gets R_1$ \Comment{... puis en lecture}
\end{algorithmic}

View File

@ -0,0 +1,4 @@
\begin{algorithmic}[1]
\State $A \gets R_1$ \Comment{Une ressource est utilisée en lecture...}
\State $R_1 \gets B$ \Comment{... puis en écriture}
\end{algorithmic}

View File

@ -0,0 +1,5 @@
\begin{algorithmic}[1]
\State $V \gets R_1$ \Comment{Introduction d'une nouvelle variable}
\State $A \gets V$ \Comment{Cette instruction ne dépend plus de la suivante}
\State $R_1 \gets B$
\end{algorithmic}

View File

@ -0,0 +1,4 @@
\begin{algorithmic}[1]
\State $R_1 \gets A$ \Comment{Une ressource utilisée en écriture...}
\State $R_1 \gets B$ \Comment{... puis à nouveau en écriture}
\end{algorithmic}

View File

@ -0,0 +1,17 @@
\begin{algorithmic}[1]
\Procedure{barrière}{} \Comment{procédure exécutée par les $N_t$ \en{threads}}
\State $\Call{P}{m}$ \Comment{sémaphore utilisé comme \en{mutex} pour protéger $N_b$}
\State $N_b \gets N_b + 1$ \Comment{$N_b$ compte le nombre de \en{thread} arrivés}
\If{$N_b = N_t$} \Comment{Le dernier \en{thread} exécutera la première branche}
\State $N_b \gets N_b - 1$
\Repeat
\State $\Call{V}{b}$ \Comment{libération des autres \en{threads}}
\State $N_b \gets N_b - 1$
\Until{$N_b = 0$}
\State $\Call{V}{m}$
\Else \Comment{pour les $N_t-1$ premiers \en{threads}}
\State $\Call{V}{m}$
\State $\Call{P}{b}$ \Comment{bloqué jusqu'à l'arrivée du dernier \en{thread}}
\EndIf
\EndProcedure
\end{algorithmic}

View File

@ -0,0 +1,13 @@
\begin{algorithmic}[1]
\Procedure{thread1}{} \Comment{procédure exécutée par l'un des deux \en{threads}}
\State ...
\State $\Call{V}{a}$
\State $\Call{P}{b}$
\EndProcedure
\Procedure{thread2}{} \Comment{procédure exécutée par l'autre \en{thread}}
\State ...
\State $\Call{P}{a}$
\State $\Call{V}{b}$
\EndProcedure
\end{algorithmic}

View File

@ -0,0 +1,8 @@
\begin{algorithmic}[1]
\State \Comment{Le sémaphore $m$ existe et a été initialisé à 1}
\Procedure{}{} \Comment{procédure exécutée par de multiples \en{threads}}
\State $\Call{P}{m}$
\State ... \Comment{section critique}
\State $\Call{V}{m}$
\EndProcedure
\end{algorithmic}

View File

@ -0,0 +1,19 @@
\begin{algorithmic}
\State entrée : un ensemble d'instructions $P = \{I_1, ..., I_n\}$
\State sortie : l'ensemble des sous-ensembles d'instructions dépendantes
\Function{GrouperInstructionsDépendantes}{$P$}
\State $G \gets \emptyset$
\ForAll{$I_k \in P$}
\State $P_a \gets \{I_k\}$
\State $P_c \gets P_a$
\ForAll{$P_b \in G$}
\If{not \Call{TestBernstein}{$P_a$, $P_b$}}
\State $G \gets G \setminus \{P_b\}$
\State $P_c \gets P_c \cup P_b$
\EndIf
\EndFor
\State $G \gets G \cup \{P_c\}$
\EndFor
\State \Return $G$
\EndFunction
\end{algorithmic}

30
src/alsk/0_intro.tex Normal file
View File

@ -0,0 +1,30 @@
\section{Introduction}
La parallélisation automatique permet au développeur de ne pas se préoccuper du tout de la
problématique de parallélisation au sein de son programme.
En contrepartie, il perd une partie du contrôle dont il pourrait disposer sur sa parallélisation.
Ce chapitre traite d'une solution de parallélisation assistée utilisant le concept de squelettes
algorithmiques~\autocite{ref:cole1989}.
L'objectif principal est de proposer une bibliothèque
active\footnote{\url{https://phd.pereda.fr/dev/alsk}} permettant à un développeur de décrire ses
algorithmes en spécifiant lui-même quelles parties peuvent être exécutées en parallèles, et de
quelle manière elles doivent l'être en assemblant un ensemble de motifs d'exécution fournis.
Ce chapitre présente d'abord la recherche existante autour des squelettes algorithmiques puis
introduit un ensemble d'outils classiques de \gls{RO} ainsi qu'un problème de \gls{RO}.
Ceux-ci sont utilisés comme application aux travaux présentés dans ce chapitre car ils ont des
propriétés intéressantes pour mettre en évidence des problématiques liées à la parallélisation et
donc les solutions proposées, notamment pour le problème de la répétabilité.
Ce chapitre se poursuit avec la présentation de notre conception des squelettes algorithmiques, en
détaillant en particulier leur structure et la possibilité de décrire les transmissions de données
entre les différentes parties de l'algorithmique représenté.
Ensuite, le chapitre explique comment se fait la répartition des tâches de l'algorithme afin de le
rendre parallèle et expose différentes stratégies pour ce faire.
Après cela, nous abordons le problème de la répétabilité, notamment pour le cas de l'utilisation de
nombres pseudo-aléatoires, et des méthodes pour optimiser la procédure initiale en tenant compte de
la stratégie de répartition des tâches adoptée et du degré de parallélisation.
Ce chapitre introduit ensuite un \gls{EDSL} servant d'interface pour simplifier l'utilisation de la
bibliothèque.
Enfin, il se termine sur une étude des performances obtenues en utilisant la bibliothèque en la
confrontant à des codes écrits sans l'utiliser.

160
src/alsk/1_relwork.tex Normal file
View File

@ -0,0 +1,160 @@
\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.

227
src/alsk/2_app.tex Normal file
View File

@ -0,0 +1,227 @@
\section{Application}
L'objectif de cette section est d'introduire les problèmes sur lesquels nous avons appliqué notre
solution.
Nous avons choisi de travailler sur la résolution de problèmes de \gls{RO}.
Ceux-ci offrent plusieurs niveaux de parallélisation qui peuvent être entrelacés avec des niveaux
non parallélisables, ce qui nous a intéressé pour valider notre représentation globale d'un
algorithme.
L'utilisation intensive de nombres pseudo-aléatoires dans ce domaine nous a également permis
d'éprouver notre solution en ce qui concerne l'obtention de résultats répétables.
Le voyageur de commerce (\gls{TSP})~\autocite{ref:robinson1949} est un problème très classique de
\gls{RO}.
Une instance de ce problème consiste en un graphe $G = (V, A, c)$$V$ est un ensemble de sommets,
$A$ un ensemble d'arêtes et $c$ une fonction de coût qui associe une distance minimale à un arc de
$A$~\autocite{ref:dantzig1954}.
Une solution peut être exprimée comme un vecteur $s$ de sommets contenant une fois chaque sommet de
$V$, sa valeur $c_s$ étant alors donnée par \acref{eq:alsk/app/tspvalue}, où $(a, b)$ correspond à
l'arête entre le sommet $a$ et le sommet $b$.
\begin{align}
c_s = \sum_{i=2}^{\vert V \vert}{c((s[i-1], s[i]))}
\label{eq:alsk/app/tspvalue}
\end{align}
L'objectif est alors de trouver un cycle qui minimise $c_s$.
\Acref{fig:alsk/app/tspinstance} est un exemple d'instance de \gls{TSP} avec \num{10} sommets pour
lequel nous considérerons que les coûts sont les distances entre les deux sommets concernés
(précisément la norme euclidienne).
Deux solutions sont montrées dans \acref{fig:alsk/app/tspsolution}, celle de droite étant optimale.
Les algorithmes peuvent être évalués selon un critère de complexité, temporelle ou spatiale.
Par extension, il est possible d'associer une complexité à un problème comme étant la meilleure de
celles des algorithmes possibles permettant de le résoudre.
Afin de catégoriser les problèmes en fonction de leur complexité, des classes de complexité ont été
définies.
Il existe par exemple la classe P dont les problèmes peuvent être résolus en temps
polynomial, et sont donc considérés comme \og faciles \fg.
En revanche, pour les problèmes de classe NP, ce n'est que la vérification d'une solution qui peut
être faite en temps polynomial.
Un problème NP-complet est un problème de classe NP qui est au moins aussi difficile que tous les
autres problèmes de la classe NP et le \gls{TSP} est NP-complet : s'il est possible de vérifier une
solution en temps polynomial, en trouver une est difficile.
Cette catégorie de problèmes peut être traitée en \gls{RO} par des heuristiques et des
métaheuristiques.
\newcommand*{\pointstotspstart}[2]{%
\hypersetup{linkcolor=black}%
\ocgmakeprefixes{#1}%
\edef\prefixP{\ocgprefix-pointer}%
\actionsocg{\prefixP}{}{}{#2}%
\tikz[remember picture,baseline=-.5ex]{\coordinate(tsp start anchor);}%
\begin{ocg}{\prefixP}{\prefixP}{0}%
\foreach \tspstart in {0,...,9} {%
\ifthenelse{\tspstart=0}{\def\state{1}}{\def\state{0}}%
\ocgcase{\prefixI}{\prefixI-\tspstart}{\state}{\tikz[remember picture,overlay]{%
\path[->,>=stealth,thick,dashed] (tsp start anchor) edge[bend right] (tsp node \tspstart);%
}}%
}%
\end{ocg}%
}
\renewcommand*{\pointstotspstart}[2]{#2}%disabled
Le principe d'une heuristique est de trouver rapidement une solution au détriment de sa qualité.
Un exemple d'heuristique est l'algorithme glouton (\cref{alg:alsk/app/greedy}).
Appliqué au \gls{TSP}, celui-ci accepte en entrée une représentation du problème $P$ contenant
notamment l'ensemble des sommets.
Une solution est alors construite progressivement en lui ajoutant le sommet jugé optimal à chaque
étape.
\Acref{fig:alsk/app/tspgreedysolution} présente la solution obtenue par l'application de cet
algorithme sur l'instance de la \acref{fig:alsk/app/tspinstance} en démarrant du
\pointstotspstart{app/tspgreedysolution}{point grisé}.
Il existe des variantes non déterministes, par exemple en considérant les $n > 1$ meilleurs ajouts
possibles à chaque étape et en ajoutant l'un d'eux au hasard.
\begin{algorithm}
\inputalg{app/greedy}
{Algorithme glouton}
\end{algorithm}
\begin{figure}
\inputfig{app/tspinstance}
{Instance de \glsxtrshort{TSP}}
\end{figure}
\begin{figure}
\inputfig{app/tspsolution}
{Solutions de l'instance de \glsxtrshort{TSP} de \acref{fig:alsk/app/tspinstance}}
\end{figure}
\begin{figure}
\ocgfigRP{Solution TSP (glouton)}
{app/tsptikzrp}{app/tspgreedysolution}
{Solution d'une instance de \glsxtrshort{TSP} par un algorithme glouton}
{\tspstart}{0/on,1/off,2/off,3/off,4/off,5/off,6/off,7/off,8/off,9/off}
{circle,minimum size=5pt,inner sep=0pt}{tsp node \tspstart}
{cliquer sur un sommet pour choisir le premier}
\end{figure}
Une métaheuristique est une heuristique générique pouvant s'appliquer à différents problèmes sans
changements importants de l'algorithme.
Les exemples sont nombreux~\autocite{ref:toussaint2010} : recuit simulé, recherche tabou, recherche
à voisinage variable, ...
Ceux qui vont nous intéresser particulièrement, étant utilisés comme exemples dans la suite de ce
chapitre, sont :
\begin{itemize}
\item le \gls{GRASP} ;
\item l'\gls{ILS} ;
\item l'\gls{ELS}.
\end{itemize}
Le \gls{GRASP}~\autocite{ref:feo1989} (\cref{alg:alsk/app/grasp}) consiste en la création de
multiples solutions à différents endroits de l'espace des solutions (l'intérêt est amoindri si les
solutions sont trop proches) suivie de l'amélioration par modifications successives (des
déplacements \og locaux \fg dans l'espace des solutions).
La création est accomplie par une heuristique constructive, par exemple un algorithme glouton non
déterministe, et l'amélioration par une recherche locale.
Une recherche locale consiste en l'exploration partielle ou complète du voisinage d'une solution
dans l'espace des solutions.
Le voisinage d'une solution est l'ensemble des solutions que l'on peut obtenir en appliquant une
transformation sur celle-ci, aussi appelée mutation.
% Une illustration commune de ceci est représentée dans \acref{fig:alsk/app/localmin}.
% Dans celle-ci, la courbe est l'espace des solutions, et lorsque l'on cherche la solution dont la
% valeur est minimale, une recherche locale sert à \og descendre \fg afin de trouver un minimum local,
% voire un minimum global.
% \begin{figure}
% \inputfig{app/localmin}
% {Recherche locale au sein d'un espace de solutions quelconque}
% \end{figure}
\begin{algorithm}
\inputalg{app/grasp}
{\Glsxtrshort{GRASP}}
\end{algorithm}
Les transformations qu'il est possible de faire dépendent du problème : dans le cadre du \gls{TSP},
il existe par exemple l'échange qui consiste en l'échange aléatoire de deux sommets dans la
solution (voir \acref{fig:alsk/app/tspswap}).
La descente est un exemple de recherche locale qui va remplacer la solution par son meilleur voisin
jusqu'à ce qu'il n'existe pas de voisin au moins aussi intéressant.
\begin{figure}
\ocgfigRP{Illustration d'échange de sommets d'une solution de TSP}
{app/tsptikzrpii}{app/tspswap}
{Échange de deux sommets d'une solution d'une instance de \glsxtrshort{TSP}}
{\swapI}{0/off,1/off,2/off,3/off,4/on,5/off,6/off,7/off,8/off,9/off}
{circle,minimum size=5pt,inner sep=0pt}{tsp node \swapI,tsp node \swapI'}
{cliquer sur un sommet pour choisir un des sommets échangés}
\end{figure}
Les différentes itérations de création (par heuristique constructive (HC)) et amélioration d'une
solution (par recherche locale (RL)) sont indépendantes et peuvent donc être exécutées en parallèle.
La sélection (S) de la solution doit en revanche être accomplie de manière séquentielle.
\Acref{fig:alsk/app/grasp} représente un \gls{GRASP} en tenant compte de cela.
Les tâches à exécuter sont représentées par des cercles tandis que les triangles indiquent une
section (ouverte par le triangle qui sépare le flot d'exécution, et fermée par le triangle qui les
réunit) dont les flots d'exécutions peuvent être exécutés en parallèle.
\begin{figure}
\inputfig{app/grasp}
{Schéma d'un \glsxtrshort{GRASP}}
\end{figure}
L'\gls{ILS}~\autocite{ref:lourenco2003} (\cref{alg:alsk/app/ils}) améliore une solution $S$ plutôt
que d'en créer une.
Dans un premier temps, la solution $S$ donnée en paramètre est améliorée au moyen d'une recherche
locale (initiale afin de la différencier de la recherche locale utilisée ensuite).
L'amélioration de cette solution est ensuite effectuée en itérant $N$ fois l'amélioration par
recherche locale d'une mutation de la solution courante $S$.
À chaque itération, la solution courante est mise à jour pour prendre la nouvelle solution créée ou
non en fonction d'un critère d'acceptation.
Si cette solution est, par ailleurs, meilleure que la meilleure solution $S^{*}$ retenue
jusqu'alors, elle la remplace.
\begin{algorithm}
\inputalg{app/ils}
{\Glsxtrshort{ILS}}
\end{algorithm}
Chaque itération de cet algorithme dépendant du résultat de l'itération précédente, il n'est pas
possible de le paralléliser.
\Acref{fig:alsk/app/ils} représente cela par l'utilisation de carrés pour la boucle.
Les tâches de mutation (M), de recherche locale (RL), de sélection (S) et de vérification du critère
d'acceptation (A) sont ainsi exécutées séquentiellement à chaque itération, elles aussi exécutées en
séquence.
\begin{figure}
\inputfig{app/ils}
{Schéma d'un \glsxtrshort{ILS}}
\end{figure}
Enfin, l'\gls{ELS}~\autocite{ref:wolf2007} (\cref{alg:alsk/app/els}) fonctionne de manière similaire
à l'\gls{ILS}.
On retrouve la recherche locale initiale et une boucle principale permettant d'améliorer une
solution $S$ donnée en paramètre.
À chaque itération, $M$ solutions sont générées par une mutation suivie d'une recherche locale et la
meilleure, si elle vérifie le critère d'acceptation, est conservée pour l'itération suivante.
Cet algorithme est plus intéressant que le précédent quant à la parallélisation.
Bien qu'il possède une boucle externe non parallélisable, la boucle interne peut l'être.
\Acref{fig:alsk/app/els} présente ainsi une structure externe très semblable à
\acref{fig:alsk/app/ils} dont la séquence \og mutation, recherche locale \fg{} est remplacée par une
tâche plus complexe introduisant une boucle parallélisable.
\begin{figure}
\inputfig{app/els}
{Schéma d'un \glsxtrshort{ELS}}
\end{figure}
Ces métaheuristiques peuvent également être combinées : \autocite{ref:prins2009a} a proposé
\graspils{} et \graspels{}, des métaheuristiques \gls{GRASP} dont la recherche locale est,
respectivement, \gls{ILS} et \gls{ELS}.
C'est en particulier le \graspels{} qui va servir d'exemple dans ce chapitre pour illustrer
l'utilisation des squelettes algorithmiques que nous proposons\footnote{Une bibliothèque
implémentant des algorithmes de \gls{RO}, notamment le \graspels{}, fait partie des travaux
effectués durant la thèse (\url{https://phd.pereda.fr/dev/rosa}).}.
Sa structure générale possède deux niveaux de boucles parallélisables entrecoupés par un niveau de
boucle devant être exécuté séquentiellement, ce qui en fait une application intéressante pour la
validation de notre modèle.
\begin{algorithm}
\inputalg{app/els}
{\Glsxtrshort{ELS}}
\end{algorithm}

7
src/alsk/3_concept.tex Normal file
View File

@ -0,0 +1,7 @@
\section{Conception}
\inputsrc{concept/0_intro}
\inputsrc{concept/1_struct}
\inputsrc{concept/2_links}
\inputsrc{concept/3_inst}
\inputsrc{concept/4_parheight}

8
src/alsk/4_exec.tex Normal file
View File

@ -0,0 +1,8 @@
\section{Politique d'exécution}
\label{sec:alsk/exec}
\inputsrc{exec/0_intro}
\inputsrc{exec/1_bones}
\inputsrc{exec/2_ident}
\inputsrc{exec/3_executor}
\inputsrc{exec/4_impl}

7
src/alsk/5_repeat.tex Normal file
View File

@ -0,0 +1,7 @@
\section{Répétabilité}
\label{sec:alsk/repeatability}
\inputsrc{repeat/0_intro}
\inputsrc{repeat/1_repeat}
\inputsrc{repeat/2_opti}
\inputsrc{repeat/3_results}

4
src/alsk/6_usage.tex Normal file
View File

@ -0,0 +1,4 @@
\section{Utilisation}
\inputsrc{usage/1_edsl}
\inputsrc{usage/2_impl}

298
src/alsk/7_results.tex Normal file
View File

@ -0,0 +1,298 @@
\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}
%}}}
%}}}

Some files were not shown because too many files have changed in this diff Show More