thesis version
This commit is contained in:
commit
6cc2e76b2a
|
@ -0,0 +1 @@
|
|||
build/
|
|
@ -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
|
||||
)
|
|
@ -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/`.
|
File diff suppressed because it is too large
Load Diff
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.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
-- only to force git to track parent directory --
|
Binary file not shown.
After Width: | Height: | Size: 160 KiB |
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,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
|
|
@ -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
|
|
@ -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}
|
|
@ -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}
|
||||
%}}}
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
||||
%}}}
|
|
@ -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}
|
|
@ -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}
|
|
@ -0,0 +1,6 @@
|
|||
\chapterx{mp}{Métaprogrammation template}
|
||||
|
||||
\inputsrc{0_intro}
|
||||
\inputsrc{1_types}
|
||||
\inputsrc{2_mpt}
|
||||
\inputsrc{3_conclusion}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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.
|
||||
%}}}
|
||||
%}}}
|
||||
|
|
@ -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}}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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.
|
|
@ -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$ où $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.
|
|
@ -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)$ où $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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -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}
|
|
@ -0,0 +1,4 @@
|
|||
\section{Utilisation}
|
||||
|
||||
\inputsrc{usage/1_edsl}
|
||||
\inputsrc{usage/2_impl}
|
|
@ -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
Loading…
Reference in New Issue