Programmation Fonctionnelle Stefano Guerrini [email protected] http://www.lipn.univ-paris13.fr/~guerrini Gestion de la mémoire LIPN - Institut Galilée, Université Paris Nord 13 Licence Info 3 février 2013 S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle Gestion de la mémoire février 2013 1 / 131 S. Guerrini (LIPN - Paris 13) Mémoire de programme Programmation Fonctionnelle Gestion de la mémoire Des zones di↵érents de mémoire février 2013 68 / 131 Mémoire de programme Garbage collection Mémoire statique I I I allouée au moment du chargement du programme (e.g., au démarrage de la boucle interactive). ne change pas de dimension, mais sont contenu peut se modifier au long de l’exécution. contient des données (ou du code) nécessaires tout au long de l’exécution du programme (e.g., le code et les données de la boucle interactive). Mémoire dynamique : le tas (en anglais, heap) I Des structures (e.g., des listes) sont ajoutées ou supprimées dynamiquement. Pile d’exécution (en anglais : stack) I I I I Comme dans le tas, des données sont ajoutées et supprimées dynamiquement. Mais, dans la pile, les données sont supprimées en respectant l’ordre inverse d’allocation (DEPS) La pile est organisée en cadres de piles (en anglais, stack frames) qui contiennent les données des appels de fonctions. La pile contient aussi des valeurs intermédiaire nécessaires seulement pendant l’évaluation di une expression de la fonction. S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle février 2013 69 / 131 En OCaml on n’a pas des commandes explicites d’allocation ou de-allocation de la mémoire. Le structures sont alloué au moyen des constructeur du type de données (e.g., pour les liste le cons ::). Quand une structure (par exemple une liste) n’est plus accessible alors son espace mémoire peut être réutilisé. C’est la tâche du Garbage Collector (GC, parfois Ramassage de miettes), qui est lancé quand le système OCaml a besoin de mémoire. Comme en Java. S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle février 2013 70 / 131 Gestion de la mémoire Mémoire de programme Gestion de la mémoire La pile Appel terminal A chaque appel de fonction, OCaml alloue un cadre sur la pile. Chaque cadre de pile stockes (parmi d’autres) I I Une fonction f appèle une fonction g et le résultat de l’évaluation de g est envoyé tout de suite par f. les valeurs locales et les paramètres d’un appel de fonction les informations nécessaires pour revenir au point de l’appel de la fonction I Quand cet appel de fonction est terminé, sa mémoire locale est balayée du sommet de la pile. Dans l’évaluation d’une fonction récursive on a un cadre de pile pour chaque appel récursif. I I Mémoire de programme Pourtant, si on fait une récursion trop profonde on risque d’épuiser l’espace disponible pour la pile. Mais attention, le risque est réel seulement si ont fait au moins des dizaines ou centaines de milliers d’appels récursives. S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle Gestion de la mémoire février 2013 71 / 131 exemple d’appel terminal : let f x = if x = 0 then 0 else g x I exemple d’appel non-terminal : let f x = x + g x Dans le cas d’appel terminal, on n’a plus besoin des valeurs locales de la fonction f quand on évalue g. Le cadre de f sur la pile peut être libéré avant d’évaluer g. S. Guerrini (LIPN - Paris 13) Mémoire de programme Programmation Fonctionnelle Gestion de la mémoire Récursion terminale février 2013 72 / 131 Mémoire de programme Réécrire une fonction en récursion terminale Il est parfois possible de réécrire une fonction récursive en une fonction récursive terminale équivalente. En anglais, tail recursion. let rec somme = function | [] -> 0 | hd :: tl -> hd + somme tl;; C’est une fonction récursive, avec tous les appels récursifs à des positions terminales. let somme ls = let rec somme_term acc = function | [] -> acc | hd :: tl -> somme_term (hd + acc) tl in somme_term 0 ls;; Normalement, il faut ajouter des paramètres à la fonction. let rec fold_left f b = function | [] -> b | h::r -> fold_left f (f b h) r;; I I Peut importe la profondeur de récurrence, l’utilisation d’espace reste constante : Une fonction récursive terminale correspond à une version itérative de la fonction. Les paramètres additionnels sont les données (l’état) modifiées à chaque itération. let fibonacci n = let rec fib n = if (n = 0) then (1,0) else let (a,b) = fib (n-1) in (a+b, a) in fst (fib n);; La fonction récursive est exécutée comme une boucle d’itération ! let fibonacci n = let rec fib (a,b) n = if (n = 0) then (a,b) else fib (a+b, a) (n-1) in fst (fib (1, 0) n);; La récursion terminale permet d’éviter des dépassements de pile (en anglais, stack overflow). S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle février 2013 73 / 131 S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle février 2013 74 / 131 Gestion de la mémoire Mémoire de programme Récursion terminale et fonctions fold La fonction fold left est récursive terminale : let rec fold_left f b = function | [] -> b | h::r -> fold_left f (f b h) r;; La fonction fold right n’est pas récursive terminale : let rec fold_right f l b = match l with | [] -> b | h::r -> f h (fold_right f r b);; S. Guerrini (LIPN - Paris 13) Programmation Fonctionnelle février 2013 75 / 131