Un arbre binaire de recherche (ABR) est un arbre binaire tel que la valeur en tout nœud est strictement supérieure aux valeurs des nœuds de son sous arbre gauche et inférieure ou égale à celles de son sous arbre droit. Les ABR sont une structure de données utilisée pour implémenter un dictionnaire, c’est à dire un ensemble totalement ordonné d’éléments. La complexité en pire cas de la recherche d’un élément dans un ABR est la profondeur de l’ABR.
Le but de ce TP est de générer un programme implémentant un ABR qui minimise le nombre moyen de comparaisons lors d’une opération de recherche (i.e. sa profondeur moyenne) ; un tel ABR est dit optimal.
- Parallélisation avec OpenMP,
- Optimisation de l'espace mémoire,
- Analyse des défauts de caches des 3 algorithmes,
- Analyse des temps d'exécution des 3 algorithmes,
Le fichier compileBST.c
est modifiable pour calculer le temps d'exécution des algorithmes (i, j, k), (j, i, k) et (d, i,k) ou tester les algorithmes avec vos propres entrées ou les benchmarks (voir time_benchmarks.sh
et check_benchmarks.sh
).
frequences.txt
recommended test values are 2 4 4
, 5 3 2
, 6 2 2
, 3 2 5
and 1 3 6
.
# compile the OBST
make binary
# run on an example with n = 3 entries
./bin/compileBST 3 frequences.txt
# clean the artefacts
make clean
Affichage des benchmarks avec gnuplot
:
$gnuplot
gnuplot> set key outside
gnuplot> set grid
gnuplot> plot for [col=1:3] 'benchmarks/benchmark6.perf' using 0:col with yerrorlines
Commandes utilisées:
$lscpu > hardware
$valgrind --tool=cachegrind --log-file=valgrind-cachegrindijk.txt ./bin/compileBST 5000 benchmarks/benchmark6.in
$sudo perf stat -e task-clock,cycles,instructions,cache-references,cache-misses,L1-dcache-loads,L1-dcache-load-misses ./bin/compileBST 5000 benchmarks/benchmark6.in
$sudo perf record -e cache-misses ./bin/compileBST 5000 benchmarks/benchmark6.in
$sudo perf report --stdio --header
# Affiche la moyenne, le minimum et le maximum d'une colonne
function colstat {
awk -v col=$1 '{if(min==""){min=max=$col}; if(col>max) {max=$col}; if($col<min) {min=$col}; total+=$col; count+=1} END {print "avg " total/count," | max "max," | min " min}' $2;
}
See the Latex source or the PDF.
On effectue un pré-calcul
/**
* La valeur à l'emplacement (i, j) dans la matrice triangulaire supérieure se trouve
* dans la mémoire à l'emplacement (i * (2 * (n - 1) - i + 1)) / 2 + j.
* Un seul accès à l'array sommes_p.
*/
#define sommes_p(i, j, n) (sommes_p[((i) * (2 * ((n) - 1) - (i) + 1)) / 2 + (j)])
/**
* Calcul des probabilitées et stockage dans un array 1d. Complexité spatiale et temporelle en O((n * (n + 1)) / 2).
*
* @param probabilites tableau des probabilités
* @param n taille de la matrice carrée générée, n == len(probabilites) + 1
*/
double *calculer_sommes_opti(double *probabilites, size_t n) {
/* On a n * (n + 1) / 2 éléments dans la matrice triangulaire supérieure avec la diagonale centrale nulle */
double *sommes_p = malloc((n * (n + 1)) / 2 * sizeof(double));
/* Calcul des sommes des probabilités */
for (size_t i = 0; i < n; ++i) {
/* Initialisation de la diagonale à p_0 */
sommes_p(i, i, n) = 0;
for (size_t j = i + 1; j < n; ++j) {
sommes_p(i, j, n) = sommes_p(i, j - 1, n) + probabilites[j - 1];
}
}
return sommes_p;
}
ou
/**
* Deux accès à l'array sommes_p et une soustraction.
*/
#define sommes_p(i, j) (sommes_p[j] - sommes_p[i])
double *sommes_p = malloc(N * sizeof(double));
double somme = 0;
for (size_t i = 0; i < N; ++i) {
somme += probabilites[i];
sommes_p[i] = somme;
}
On a
On sait que tout sous arbre d'un ABR optimal est un ABR optimal. On choisit donc de prendre comme racine tous les noeuds de l'ABR en faisant varier
On peut aussi le voir de cette façon:
Il y a un total de
Nous considérons maintenant la recherche dans un arbre binaire équilibré contenant
Pour stocker notre arbre binaire, on utilise un tableau bst[n][2]
. On ne stocke pas la racine, obtenu en C(0, n - 1)
, et on stocke les fils gauche et droit du noeud stocké en bst[i]
dans les cases bst[?][0]
et bst[?][1]
.
Dès que l’on s’éloigne de la racine, une ligne de cache ne contient que des noeuds qui ne sont pas sur le même chemin racine-feuille. On a donc besoin d’un défaut de cache par noeud de l’arbre pour un coût total de :