Envoyé par
bluestorm
Tout à fait. Pour donner des chiffres, il y a trois fonctions "représentatives" de l'activité du GC, mark_slice, sweep_slice et caml_oldify_mopup. Pour un lancement sur 2_000_000 :
- mark_slice : 2) 223 appels, 3) 301 appels
- sweep_slice : 2) 144, 3) 188
- olidify : 2) 367, 3) 489
La version avec continuation fait plus travailler le GC.
Avec le tas augmenté, la 2 est meilleure, mais de "peu de choses" parce que les temps d'exécution sont courts. Il y a quand même au moins 20% de différences de performance, ce qui n'est pas négligeable (bien que pas pharaonique).
Cependant, la solution des continuations est tout de même moins bonne car elle utilise nettement plus de mémoire. Si j'augmente encore l'entrée (de 2_000_000 à 5_000_000), elle réclame vraiment trop de mémoire et son comportement devient erratique (freeze, Out_of_memory, tué par l'OS, etc.). Je n'ai pas les détails précis car la mesure de la consommation mémoire est délicate et je ne connais pas trop ce domaine. En gros, à partir d'un certain niveau, la consommation mémoire joue de toute façon sur les performances et la solution avec continuation devient beaucoup moins intéressante.
Ceci dit, la facilité de l'optimisation "tranches de 10" rend la version par continuations tout de même très pertinente.
Par ailleurs, il est possible de "réifier" les continuations en utilisant, au lieu d'une fermeture ocaml directement, un simple type algébrique modélisant cette fermeture (et plus légère en mémoire). C'est une transformation assez classique, et j'ai l'intuition (je n'ai pas vérifié mais si ça t'intéresse je peux toujours regarder (après manger)) qu'elle donnerait, à quelques manipulations équationnelles près, précisément le programme tail-récursif.
Partager