INFO 2014 Fichiers et base de données Philippe Fournier-Viger Département d’informatique, U.de Moncton Bureau D216, philippe.fournier-viger@umoncton.ca Hiver 2015
Rappel: Le TRI INTERNE
Le tri interne Tri interne: le tri de données en mémoire vive. Entrée : une liste d’éléments Sortie: une liste d’éléments triée Algorithmes O(n2) Tri par insertion « bubble sort » Tri sélection … Algorithmes O(n log(n)) Tri rapide (Quicksort): pire cas O(n2) Tri fusion (merge sort) Tris par tas (heap sort)
Tri fusion Illustration Animation: src: wikipédia
Tri fusion (suite) List mergesort(List list)}{ if(list.length() <= 1} return list; List l1 = la moitié des éléments de list; List l2 = l’autre moitié; return merge(mergesort(l1), mergesort(l2)); } merge: fusion de deux listes en comparant item par item
Tri fusion (suite) merge(List n1, List n2)}{ List resultat; tant que n1 ou n2 ne sont pas vides. si le premier élément de n1 est inférieur ou égal au premier élément de n2 retirer le premier élément de n1 et l’ajouter à resultat. sinon retirer le premier élément de n2 et l’ajouter à resultat. concatener n1 et n2 à resultat. retourner resultat; }
Tri fusion (suite) merge: O(n) n = nombre d’éléments fusionnés mergesort: O(n log (n)) Propriétés: algorithme « diviser-pour-régner »
Tri rapide Idée: Vidéos: Choisir un élément appelé pivot pour séparer le tableau en deux. Réorganiser les éléments tel que tous les éléments plus petits ou égaux au pivot soient dans la partie gauche du tableau et tous les éléments plus grand soient dans la partie droite . Répéter récursivement pour la partie gauche et la partie droite. Vidéos: http://www.youtube.com/watch?v=y_G9BkAm6B8&fe ature=relmfu (anglais) http://www.csanimated.com/animation.php?t=Quickso rt (anglais)
Tri rapide (suite) Illustration: Un algorithme diviser pour régner Animation: src: wikipédia
LE TRI EXTERNE (Livre – section 8.5)
Algorithmes pour données sur disque Parfois, les données à traiter ne peuvent pas être stockées entièrement en mémoire centrale. Solution: algorithmes utilisant la mémoire secondaire. Pour concevoir de tels algorithmes efficients, il faut minimiser les accès disque.
Étude de cas: le tri externe Tri externe: tri d’une liste de données stockée en mémoire secondaire qui ne peut pas être chargée entièrement en mémoire vive. Plusieurs applications. Ex.: tri de tables dans les base de données relationnelles. Défi: on ne peut charger qu’un petit ensemble de données en mémoire vive à la fois, on souhaite utiliser une quantité de mémoire raisonnable. Comment faire un algorithme efficient (temps, mémoire) pour le tri externe?
Utiliser la mémoire virtuelle Une approche simple: Utiliser la mémoire virtuelle (mémoire vive simulée sur le disque dur) pour permettre d’utiliser plus de mémoire vive. Appliquer un algorithme de tri interne tel que le tri rapide. Problèmes: large consommation de mémoire vive, la mémoire virtuelle pourrait être insuffisante car parfois limitée en taille, même avec un bassin de tampon, un algorithme comme le tri rapide nécessiterait de lire et écrire log(n) fois chaque enregistrement sur le disque en moyenne. Pourquoi log(n) écriture / lecture, parce que c’est un algorithme diviser pour regner qui traite une liste en la divisant récursivement en sous-liste. C’est donc comme un arbre binaire à log(n) niveau, mais pour chaque niveau on ne Pour chaque niveau il faut écirre et lire une liste sur le disque.
Pour une meilleure approche… Idée de base: Lire un ensemble d’enregistrements en mémoire vive. Réordonner les enregistrements. Recommencer avec un autre ensemble d’enregistrements. … Nous allons définir un algorithme basé sur cette idée. De plus, nous tenterons de minimiser le nombre d’accès disque, car c’est ce qui est le plus coûteux.
Hypothèses Un seul disque dur. Un fichier est divisé en blocs. Un bloc contient plusieurs enregistrements. La taille des blocs est un multiple de la taille des secteurs. Les enregistrements ne sont jamais coupés en deux blocs. Ainsi, un ou des blocs peuvent être lus en mémoire, les enregistrements peuvent être triés, puis le(s) bloc(s) peuvent être écris sur le disque.
Est-ce qu’on devrait tenter de lire les blocs séquentiellement? L’accès séquentiel est plus rapide qu’un accès aléatoire, mais en pratique: l’utilisateur a peu de contrôle sur la représentation physique des fichiers, la tête de lecture est souvent déplacée par des accès concurrents au disque, l’algorithme de tri lui-même déplace la tête de lecture pour écrire les données au disque. Ainsi, (pour un seul disque) il est préférable de chercher à minimiser le nombre d’accès disque en considérant un accès aléatoire plutôt que de préférer un plus grand nombre d’accès disque avec un accès séquentiel.
Est-ce qu’on devrait charger des enregistrements au complet en mémoire ? Charger les enregistrements au complet en mémoire pour les trier consommerait beaucoup de mémoire vive. Une meilleure approche: créer un index de paires (1) clé et (2) pointeur vers un enregistrement. trier l’index en utilisant la clé. réordonner les enregistrement ou non (car d’autres tris pourraient être effectués éventuellement, sur la même clé ou une autre). A00123123 Jean Poirier 18 ans …
Illustration Fichier (sur le disque dur) Index sur la clé « âge » (en mémoire vive pour le tri par âge) A00123123 Jean Poirier 18 ans … A00123124 Paul Lalonde 25 ans … A00123125 Marie Poirier 17 ans … A00123126 Patricia Turcotte 25 ans … A00123123 18 ans A00123124 25 ans A00123125 17 ans A00123126 25 ans
Approche basée sur le tri fusion Idée principale: on commence par des listes de taille 1. les listes de taille 1 sont fusionnées pour obtenir des listes de taille 2 triés, les listes de taille 2 sont fusionnées pour obtenir des listes de taille 4 triés, … des listes de taille 8 triés …. finalement, on obtient une liste de taille n triée.
Approche basée sur le tri fusion Séparer le fichier en deux fichiers de même taille. sur disque dur:
Approche basée sur le tri fusion Séparer le fichier en deux fichiers de même taille. Lire un bloc de chaque fichier dans un tampon d’entrée. sur disque dur: sur disque dur: en mémoire vive (tampons d’entrée)
Approche basée sur le tri fusion Prendre le premier enregistrement de chaque tampon et écrire une liste triée de 2 enregistrements dans un tampon de sortie. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Prendre le prochain enregistrement de chaque tampon et écrire une liste triée de taille 2 dans un autre tampon de sortie. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Prendre le prochain enregistrement de chaque tampon et écrire une liste triée de taille 2 dans un autre tampon de sortie. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Prendre le prochain enregistrement de chaque tampon et écrire une liste triée de taille 2 dans un autre tampon de sortie. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Répéter les étapes 3 et 4 en alternant entre les deux tampons de sortie. Si un tampon de sortie est plein, le vider. Si tous les enregistrements d’un bloc ont été traité, prendre le prochain bloc.
Approche basée sur le tri fusion Ensuite prendre les deux fichiers écrits en entrée et répéter le même principe pour former des listes de 4 enregistrements. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Ensuite prendre les deux fichiers écrits en entrée et répéter le même principe pour former des listes de 4 enregistrements. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Ensuite prendre les deux fichiers écrits en entrée et répéter le même principe pour former des listes de 4 enregistrements. en mémoire vive (tampons d’entrée) en mémoire vive (tampons de sortie)
Approche basée sur le tri fusion Ensuite prendre les deux fichiers écrits en entrée et répéter le même principe pour former des listes de 8 enregistrements…. en mémoire vive (tampons d’entrée) en mémoire vive (tampon de sortie)
Approche basée sur le tri fusion Ensuite prendre les deux fichiers écrits en entrée et répéter le même principe pour former des listes de 8 enregistrements…. en mémoire vive (tampons d’entrée) en mémoire vive (tampon de sortie) La liste est triée!
Lecture séquentielle possible? Observation: Les blocs sont lus/écrits dans les fichiers d’entrée/sortie de façon séquentielle. Peut-on tirer avantage de cela pour faire une écriture/lecture séquentielle? Oui, mais il faudrait idéalement une tête de lecture pour chaque fichier, donc 4 disques dur. Cela pourrait être combiné avec une mise en tampon multiple.
Réduire le nombre d’itérations? Observation: Trier une liste de n enregistrements demande de faire log n itérations. Donc, chaque enregistrement doit être lu et écrit en moyenne log n fois. Optimisation: utiliser un algorithme de tri interne comme le tri rapide pour trier chaque bloc en mémoire, et appliquer le tri fusion en commençant avec des blocs plutôt que des listes de 1 enregistrements. gain de performance
Exemple Considérons 262 144 enregistrements répartit sur 512 blocs de 512 enregistrements. Notre algorithme de tri demande 18 itérations au total, car 218 = 262 144. Si chaque bloc est lu et trié par le tri rapide en mémoire, cela ne prendra qu’une seule itération par bloc au lieu de 9. cela diminue le nombre d’itérations total à 9 plutôt que 18. donc le nombre de lectures/écritures est réduit de moitié!
Réduire le nombre d’itérations? (suite) Dans la diapositive précédente, nous avons suggéré d’appliquer le tri rapide en mémoire vive sur chaque bloc pour le trier, et ainsi réduire le nombre d’itérations. On pourrait améliorer cette approche. Si M mégaoctets de mémoire vive sont disponibles, alors on rempli la mémoire vive de blocs et on applique le tri rapide sur l’ensemble des blocs. Cela va créer des listes initiale de taille M et ainsi réduire encore davantage le nombre d’itérations.
Réduire le nombre d’itérations? (suite) mais est-ce qu’on pourrait créer des listes initiales triés de taille supérieure à M (la mémoire vive disponible)? Oui !
Stratégie 1 POUR AUGMENTER LA TAILLE DES LISTES INITIALES
Sélection avec remplacement La sélection avec remplacement est un algorithme qui est une variation du tri par tas (heap sort). L’algorithme utilise (1) un tableau de taille M, (2) un tampon d’entrée et (3) un tampon de sortie: Tampon d’entrée Tampon de sortie tableau
Sélection avec remplacement Initialement, le tableau est rempli avec M enregistrements. Puis le traitement débute. Le tampon de sortie est vidée quand il est plein. Le tampon d’entrée est rempli avec le(s) prochain(s) bloc(s) quand il est vide. Tampon d’entrée Tampon de sortie tableau
Sélection avec remplacement Le tableau est une structure de données de type tas-min (min-heap). Tampon d’entrée Tampon de sortie tableau Le minimum est à la racine du tas
Rappel – Tas-min (min-heap) Propriétés Arbre binaire complet, Pour un nœud A et B, si A est le parent de B, alors valeur(A) ≤ valeur(B) Est stocké sous la forme d’un tableau. le minimum insertion O(log(n)) minimum O(1) supprimer O(log(n)) In computer science, a min-max heap is a double-ended priority queue implemented as a modified version of a binary heap. 1, 2, 3, 17, 19, 25, 100, 3, 36, 7 image: wikipedia
Algorithme Lire M enregistrements pour construire le tas initial. Dernier = M-1. Répéter jusqu’à ce que le tas soit vide: Prendre le minimum du tas et le mettre dans le tampon de sortie Lire le prochain enregistrement R dans le tampon d’entrée. Si la valeur de la clé de R est supérieure à l’enregistrement qui vient d’être mis en tampon de sortie, alors placer R à la racine, sinon remplacer la racine avec l’enregistrement en position DERNIER et placer R en position DERNIER. Puis DERNIER = DERNIER – 1. Réordonner le tas.
Exemple Entrée Mémoire Sortie 35, 14, 29, 16
Exemple Entrée Mémoire Sortie 35, 14, 29, 16 L’algorithme retire le minimum du tas et le place en tampon de sortie.
Exemple Entrée Mémoire Sortie 35, 14, 29, 16
Exemple Entrée Mémoire Sortie 35, 14, 29, 16 Le premier enregistrement en entrée est ajouté au tas (car 16 > 12)
Exemple Entrée Mémoire Sortie 35, 14, 29
Exemple Entrée Mémoire Sortie 35, 14, 29 16, 12 L’algorithme retire le minimum du tas et le place en tampon de sortie.
Exemple Entrée Mémoire Sortie 35, 14, 29 16, 12
Exemple Entrée Mémoire Sortie 35, 14, 29 16, 12 Le premier enregistrement en entrée est ajouté au tas (car 29 > 16)
Exemple Entrée Mémoire Sortie 29 35, 14 16, 12
Exemple Entrée Mémoire Sortie 35, 14 16, 12 29 35, 14 16, 12 Le tas est réordonné, car la propriété du tas n’est pas respecté.
Exemple Entrée Mémoire Sortie 35, 14 16, 12 29 35, 14 16, 12 La réorganisation du tas est complétée.
Exemple Entrée Mémoire Sortie 35, 14 19, 16, 12 29 35, 14 19, 16, 12 L’algorithme retire le minimum du tas (19) et le place en tampon de sortie.
Exemple Entrée Mémoire Sortie 29 35, 14 19, 16, 12
Exemple Entrée Mémoire Sortie 35, 14 19, 16, 12 29 Puisque 14 est inférieur à la dernière valeur en sortie, il n’est pas ajouté au tas mais mis de côté. Puis, le tas est réorganisé…
Exemple Entrée Mémoire Sortie 35 19, 16, 12 29 La réorganisation est terminée
Exemple Entrée Mémoire Sortie 35 21, 19, 16, 12 29 35 21, 19, 16, 12 L’algorithme retire le minimum du tas et le place en tampon de sortie. et l’algorithme continue ainsi ……
Traitement Le tas va diminuer de taille progressivement. En même temps, le tas est rempli d’enregistrements qui seront utilisés pour la prochaine itération Il est clair que la taille d’une liste en sortie est au moins M, mais de façon générale, on peut s’attendre à quoi comme taille ?
Analogie de la déneigeuse (1) Une déneigeuse déneige une piste circulaire. La neige tombe de façon constante.
Analogie de la déneigeuse (2) À chaque instant, il y a S neige sur la piste Il y en a davantage en avant de la déneigeuse et moins en arrière.
Analogie de la déneigeuse (3) Quand la déneigeuse termine un tour, il reste toujours S neige. Puisqu’elle a enlevé la neige présente (S) + la moitié de la neige qui tombe, il y a donc tombé 2S neige.
Analogie de la déneigeuse (4) La déneigeuse a donc ramassé au total 2S neige (la moitié qui tombe (2S / 2) + S la neige qui était déjà sur la piste. 63
Analogie de la déneigeuse Pour l’algorithme de tri externe: Initialement toutes les valeurs sont plus grande que la dernière valeur en sortie (la neige tombe devant la déneigeuse). Progressivement, de moins en moins de valeurs seront plus grande (de moins en moins de neige va tomber devant la déneigeuse si on considère une piste non circulaire). La taille attendue est donc d’environ 2M.
STRATÉGIE 2 FUSION MULTIPLE
Introduction Deux phases du tri externe: Observation: pour la phase 2, Phase 1: créer des listes initiales aussi longues que possible. Phase 2: fusionner récursivement les listes générées dans la première phase. Observation: pour la phase 2, S’il y a R listes à fusionner, il faudra log R passes à travers le fichier pour obtenir la liste finale triée. Peut-on réduire davantage le nombre de passes? Oui
La fusion multiple Fusion multiple: Si B listes à fusionner, un bloc de chacune est mis en mémoire, et la plus petite valeur parmi la première de chaque bloc est choisie. Quand un bloc est vide, le prochain bloc de la même liste est chargé en mémoire. Avantage: les fusions créent des listes plus grandes!
Fusion multiple – nombre de fichiers Conceptuellement, la fusion multiple fusionne plusieurs listes stockées dans des fichiers séparés. Toutefois, ce n’est pas obligatoire. Comment effectuer la fusion multiple avec un seul fichier? Il faut connaître la position de chaque liste dans le fichier et utiliser seekg() pour atteindre le bloc désiré.
Fusion multiple – type d’accès Si un seul disque dur, la fusion multiple fait des accès aléatoires plutôt que séquentiels. Pas un problème, car pour un seul disque dur, la lecture est rarement séquentielle. En fait, la fusion multiple va remplacer plusieurs lectures potentiellement séquentielles du fichier par une seule lecture aléatoire.
Fusion multiple – gain en performance Cas 1. S’il y a R listes à fusionner et qu’il est possible de charger en mémoire vive un bloc de chaque liste, une seule passe du fichier permet de fusionner toutes les listes. Cas 2. S’il y a de la place pour seulement B blocs en mémoire vive tel que B < R, la stratégie suivante peut être utilisée: les listes sont fusionnées par groupe de B listes, ensuite, les listes résultantes sont fusionnées B listes à la fois, et ceci est répété. Le nombre de passes sur le fichier dépendra de B.
Phase 1: Sélection avec remplacement Pour une quantité de mémoire vive donnée B, quelle taille de fichier peut-t-on trier avec la fusion multiple? Phase 1: Sélection avec remplacement Supposons que B blocs sont alloués pour le tas en mémoire vive. Les listes initiales générées ont une taille 2B en moyenne (par l’analogie de la déneigeuse). 2B2 car en phase 1 on a des listes de taille B. 2B + 2B + 2B… + 2B ( B fois)= 2B^2. 2Bk+1 car en fait la taille de la liste en sortie est multiplié par B après une fusion multiple. Initialement 2B après la phase 1, après la deuxième phase 2B^2 après la troisième phase 2B^3 ….
Pour une quantité de mémoire vive donnée, quelle taille de fichier peut-t-on trier avec la fusion multiple? Phase 2: Fusion multiple Supposons que cela est suivi de fusions multiples à B blocs. Si une seule fusion multiple, la taille des liste(s) générée(s) sera (2B) x B = 2B2. Si deux fusions multiples, la taille des liste(s) générée(s) sera (2B2) x B = 2B3. Si k fusions multiples, la taille de(s) liste(s) générée(s) sera: (2B) x Bk= 2Bk+1. 2B2 car en phase 1 on a des listes de taille B. 2B + 2B + 2B… + 2B ( B fois)= 2B^2. 2Bk+1 car en fait la taille de la liste en sortie est multiplié par B après une fusion multiple. Initialement 2B après la phase 1, après la deuxième phase 2B^2 après la troisième phase 2B^3 ….
Exemple Soit des blocs de 4 Ko. 0.5 Mo de mémoire vive (128 blocs) Phase 1: La taille d’une liste est 1 Mo en moyenne. Phase 2: En une passe, 128 listes peuvent être fusionnées. Donc un fichier de 128 Mo peut être trié en deux passes: une pour construire les listes initiales (phase 1) et une fusion multiple pour les fusionner (phase 2).
Exemple (suite) Si on fait deux fusion multiples plutôt qu’une seule: Pour la première fusion 1 Mo x 128 = 128 Mo Pour la deuxième fusion: 128 Mo x 128 = 16,384 Mo. En d’autres mots, il est possible de trier 16 giga- octets avec 0.5 Mo de mémoire vive en trois passes sur le fichier!
Exemple (suite) Qu’arrive-t-il si on modifie la taille des blocs (en supposant une taille de mémoire vive fixe)? Plus la taille des blocs est grande, plus cela réduit le nombre de fichiers qui peuvent être fusionnés en une seule passe. Plus la taille des blocs est petite, plus il faudra faire d’accès disque pour lire les prochains blocs.
Résultats expérimentaux Cette expérience compare la performance de trois algorithmes (sort1, sort2 et sort3) pour des fichiers de taille 1, 4, 16 et 256 Mo. Chaque entrée dans la table indique (1) le temps d’exécution et (2) la quantité de mémoire vive utilisée (en termes de blocs)
Résultats expérimentaux sort1: le tri externe avec le tri fusion standard à 2 listes sort2: le tri externe en utilisant la sélection avec remplacement pour générer de longues listes . sort3: le tri externe en utilisant la sélection avec remplacement et la fusion multiple.
Résultats expérimentaux (suite) On peut observer qu’une fusion multiple à 4 augmente beaucoup la performance, mais une fusion à 16 n’aide pas vraiment à cause du coût des comparaisons.
CONCLUSION
Principes généraux du tri externe Un bon algorithme de tri externe va: Créer initialement des listes les plus longues possibles À toutes les étapes, combiner entrée, traitement et sortie autant que possible. Utiliser autant de mémoire vive que possible. Cela améliore généralement la performance plus que la vitesse du processeur. Si possible, utiliser des disques supplémentaires pour assurer un traitement séquentiel. Overlapping input, processing, and output is sometimes called double buffering. Not all operating systems allow this at the programmer’s level.
Bibliographie Shaffer Clifford, A practical introduction to data structures and algorithm analysis, edition 3.2. http://people.cs.vt.edu/shaffer/Book/ http://www.cplusplus.com/doc/tutorial/files/ Dupin, S., Le langage C++.