Précédent Index Suivant

Exercices

Suivi de l'évolution du tas

Pour suivre l'évolution du tas, on se propose d'écrire une fonction qui conserve les informations sur le tas sous la forme d'un enregistrement de la forme suivante :

# type tr_gc = {etat : Gc.stat;
temps : float; numero : int};;
Le temps correspond au nombre de millisecondes depuis le début de l'exécution et le numéro sert à différencier les appels. On utilise la fonction Unix.time (voir chapitre 18, page ??) qui fournit le temps écoulé en millisecondes.
  1. Écrire une fonction trace_gc qui retourne un tel enregistrement.

    # type tr_gc = {etat : Gc.stat; temps : float; numero : int} ;;
    type tr_gc = { etat: Gc.stat; temps: float; numero: int }
    # let trace_gc =
    let num = ref 0 in
    function () -> incr num;
    { etat = Gc.stat (); temps = Unix.time(); numero = !num} ;;
    val trace_gc : unit -> tr_gc = <fun>


  2. Modifier cette fonction pour qu'elle puisse sauver la valeur de type tr_gc dans un fichier sous forme d'une valeur persistante. Cette nouvelle fonction a besoin d'un canal en sortie pour écrire. On utilisera le module Marshal, décrit à la page ??, pour la sauvegarde des informations.

    # let trace_out_gc oc =
    let trgc = trace_gc in
    Marshal.to_channel oc trgc [];;
    val trace_out_gc : out_channel -> unit = <fun>


  3. Écrire un programme autonome, prenant en entrée un nom de fichier de données de type tr_gc et affiche le nombre de GC mineurs et majeurs.

    type tr_gc = {etat : Gc.stat; temps : float; numero : int} ;;

    let rec last l = match l with
    [] -> failwith "last"
    | [e] -> e
    | t::q -> last q;;

    let visu ltgc =
    let fst = List.hd ltgc
    and lst = last ltgc in
    let nb_minor = lst.etat.Gc.minor_collections - fst.etat.Gc.minor_collections
    and nb_major = lst.etat.Gc.major_collections - fst.etat.Gc.major_collections in

    Printf.printf "Nombre de Gc: mineur = %d, majeur = %d\n" nb_minor nb_major ;;


    let rec read_trace ic =
    try
    let tgc = ( (Marshal.from_channel ic) : tr_gc) in
    tgc :: (read_trace ic)
    with
    End_of_file -> close_in ic; [];;

    let usage () =
    Printf.printf "usage: visu filename\n";;

    let main () =
    if Array.length (Sys.argv) < 2 then usage()
    else
    let ltgc = read_trace (open_in Sys.argv.(1)) in
    visu ltgc;;


    main();;









  4. Tester ce programme en créant un fichier de trace au niveau de la boucle d'interaction.

Allocation mémoire et styles de programmation

Cet exercice compare l'influence des styles de programmation sur l'évolution du tas. Pour cela on reprend l'exercice sur les nombres premiers du chapitre 8 page ??. On cherche à comparer deux versions, une récursive terminale (voir page ??) et l'autre non, de la fonction du crible d'Eratosthène.

  1. Écrire la fonction récursive terminale erart qui calcule les nombres entiers d'un intervalle donné. Écrire ensuite une fonction erart_go qui prend un entier et retourne la liste de nombres premiers inférieurs. fichier eras2.ml :

    (* fichier eras2.ml *)

    let erart l =
    let rec erart_aux l r = match l with
    [] -> List.rev r
    | p::q -> erart_aux (List.filter ( fun x -> x mod p <> 0) q) (p::r)
    in
    erart_aux l [] ;;

    let erart_go n =
    erart (Interval.interval (<) (fun x -> x + 1) 2 n) ;;





  2. Écrire un programme qui prend un nom de fichier et une liste de nombres sur la ligne de commande et calcule pour chaque nombre la liste de nombres premiers inférieurs à celui-ci en utilisant la fonction précédente. Cette fonction effectue une trace du GC dans le fichier indiqué. On regroupe dans le fichier trgc.ml les commandes de trace de l'exercice précédent. fichier era2_main.ml :

    (* fichier : era2_main.ml *)

    open Trgc;;

    let usage () = Printf.printf "Usage: testgc filename n1 [n2 n3 ... np]n\n";;

    let main f =
    if Array.length (Sys.argv) < 3 then usage()
    else
    let fn = Sys.argv.(1)
    and vn = Array.map int_of_string
    (Array.sub Sys.argv 2 (Array.length Sys.argv - 2)) in
    let oc = open_out fn in
    Array.map (fun n ->
    let r = f n in
    trace_out_gc oc;
    r) vn ;
    close_out oc ;;







    fichier main_rt.ml :

    (* fichier main_rt.ml *)

    Era2_main.main Eras2.erart_go;;
    (* fichier trgc.ml *)

    type tr_gc = {etat : Gc.stat; temps : float; numero : int} ;;
    let trace_gc =
    let num = ref 0 in
    function () -> incr num;
    { etat = Gc.stat (); temps = Unix.time(); numero = !num} ;;

    let trace_out_gc oc =
    let trgc = trace_gc () in
    Marshal.to_channel oc trgc [];;


  3. Compiler ces fichiers en créant un exécutable autonome et le tester sur l'appel suivant puis afficher la trace obtenue. .
    erart trace_rt 3000 4000 5000 6000
    
    Compilation pour Unix :
    $ ocamlc -custom -o erart unix.cma interval.ml trgc.ml eras2.ml era2_main.ml main_rt.ml -cclib -lunix
    
    
    Test :
    $ erart traceRT 3000 4000 5000 6000
    
    Résultats :
    $ visu traceRT 
    Nombre de Gc: mineur = 130, majeur = 22
    


  4. Effectuer le même travail pour la fonction non récursive terminale. On reprend le fichier eras.ml, de la page ??, contenant une fonction de calcul non récursive terminale.

    Puis on crée le fichier main_nrt.ml suivant :
    (* fichier main_nrt.ml *)

    Era2_main.main Eras.era_go;;


    Compilation :
    $  ocamlc -custom -o eranrt unix.cma interval.ml trgc.ml eras.ml era2_main.ml main_nrt.ml -cclib -lunix
    
    Test :
    $ eranrt traceNRT 3000 4000 5000 6000
    
    Résultats (version 2.04/Unix) :
    $ visu traceNRT 
    Nombre de Gc: mineur = 130, majeur = 10
    


  5. Comparer les résultats de la trace. Les deux programmes allouent les mêmes données : ils ont le même nombre de GC mineurs. La version récursive terminale effectue plus de GC majeurs. En effet, l'accumulateur r de la fonction erart_rt est évalué dans l'appel récursif et sa tête de liste est rangée dans la zone des valeurs anciennes à chaque GC mineur. Le filtrage des multiples d'un nombre peut entraîner un GC mineur qui fait vieillir la liste filtrée en cours de construction. Les anciennes listes filtrées sont libérées pendant un GC majeur. Ainsi l'espace mémoire de la génération ancienne qui contient la liste des nombres premiers déjà trouvés est plus petit ce qui augmente le nombre de GC majeurs.

    Ce cas est particulier

    L'écriture d'une fonction récursive sous forme récursive terminale fait gagner en allocation de pile, mais pas forcément en nombre de GC.

Précédent Index Suivant