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.
- É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>
- 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>
- É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();;
- 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.
-
É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)
;;
- É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
[];;
- 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
- 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
- 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.