• Aucun résultat trouvé

7.2 Mesures de performances d’OCaLustre

7.2.2 Résultats

À partir du nœud eightbits_adder défini ci-dessus, nous réalisons un programme OCaLustre qui calcule cent mille fois la somme 0b11111111+ 0b11111111. Cet exemple est alors exécuté, de la même façon que les tests précédents, à l’aide de la machine virtuelle OMicroB, sur PC.

La table 7.6 illustre les mesures de performances réalisées sur ce programme. Notamment, l’empreinte mémoire d’OCaLustre est faible : une pile de 80 mots, et un tas de 400 valeurs OCaml suffisent pour l’exécution d’un tel programme, pour une consommation RAM totale de seulement 1069 octets, et une consommation en flash de 7668 octets.

D’un point de vue de la vitesse d’exécution les performances des programmes OCaLustre dans OMicroB sont bonnes, mais néanmoins ralenties par le nombre important de déclenchements du GC. Comme l’illustre le graphique de la figure 7.5, un tas plus grand permet d’en réduire drastiquement le nombre, et ainsi d’accélerer l’exécution du programme.

L’option de compilation -na d’OCaLustre permet de générer un code « impératif » optimisé pour l’embarqué critique, au sens où il n’entraîne pas d’allocations de nouvelles valeurs au cours d’un instant

7.2. Mesures de performances d’OCaLustre 183

Durée d’exécution Vitesse Nombre de

Nom (secondes) (millions d’instr./seconde) GC

adders.ml 0.54 138.01 550054

Table 7.6 – Mesures de vitesse de l’additionneur avec OMicroB (sur PC)

Options d’OMicroB : -arch 16 -gc SC -stack-size 80 -heap-size 400

0 1000 2000 3000 4000 5000 6000 7000 8000 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6

Taille du tas [mots]

T

emps

d’exécution

[s]

adder.ml

GC Stop & Copy GC Mark & Compact

Figure 7.5 – Durée d’exécution de l’additionneur en fonction de la taille du tas (sur PC)

synchrone, et évite ainsi le déclenchement du garbage collector pendant l’exécution du programme synchrone. De ce fait, la vitesse d’exécution de l’additionneur est grandement accélérée lors de l’utilisation de cette option (table 7.7).

Durée d’exécution Vitesse Nombre de déclenchements

Nom (secondes) (millions d’instr./seconde) du GC

adder-noalloc.ml 0.21 383.14 0

Table 7.7 – Vitesse de l’additionneur sans allocations sur le tas en cours d’instant

Options d’OMicroB : -arch 16 -gc SC -stack-size 80 -heap-size 400

Élements de comparaison avec Lucid Synchrone : Nous proposons désormais de comparer les perfor-mances de ce programme avec celui d’un programme équivalent réalisé dans le langage Lucid Synchrone. Bien que le développement de ce langage soit aujourd’hui abandonné, son compilateur (dans sa version

3, datant d’avril 2006) reste disponible sur le site du projet [⚓19]. Ce dernier produit du code OCaml, permettant la comparaison avec OCaLustre. Cette comparaison permet principalement de vérifier le bien fondé de l’utilisation d’OCaLustre pour la programmation de matériel à faible ressources, en s’assurant que les programmes synchrones réalisés n’auraient pas pu être écrits en Lucid Synchrone tout en conser-vant des performances équivalentes. Bien entendu, Lucid Synchrone est une extension riche de Lustre, dont l’expressivité est supérieure à celle d’OCaLustre, et sa richesse se répercute aussi dans la complexité du code généré. La comparaison proposée ici ne tient donc pas lieu de comparatif des performances brutes des deux langages, mais d’ illustration du gain en place qu’apporte l’usage de notre extension de syntaxe sur les programmes que nous visons, de par sa relative simplicité. La figure 7.8 représente les mesures réalisées sur le programme Lucid Synchrone adder.ls (dont le code – très proche de la syntaxe d’OCaLustre – est donné en annexe B), ainsi que sur le programme OCaLustre dans sa version standard (adder.ml), et sa version optimisée pour l’embarqué critique (adder-na.ml). Les besoins mémoire pour permettre l’exécution du programme Lucid Synchrone étant supérieurs, les mesures ont été effectuées avec une pile de 150 mots, et un tas de 600 mots.

Durée d’exécution Vitesse Nombre de déclenchements

Nom (secondes) (millions d’instr./seconde) du GC

adder.ls 0.79 222.87 440042

adder.ml 0.25 298.11 110010

adder-na.ml 0.19 423.47 0

Table 7.8 – Vitesses des programmes Lucid Synchrone et OCaLustre (PC)

Options d’OMicroB : -arch 16 -gc SC -stack-size 150 -heap-size 600

Il résulte de ces mesures que sur ce petit programme, à configurations de la machine virtuelle égales, les performances de Lucid Synchrone sur cet exemple sont trois à quatre fois moindres que celles d’OCaLustre sur PC. Sur un ATmega2560, les performances en vitesse de Lucid Synchrone sont 4 à 6 fois inférieures aux programmes OCaLustre4 (table 7.9). En effet, de par la richesse du langage, le code OCaml généré par Lucid Synchrone est bien plus conséquent et plus gourmand en ressources que celui généré par OCaLustre : la figure 7.6 représente, pour le nœud twobits_adder le code OCaml généré par Lucid Synchrone, et celui généré par compilation OCaLustre. En particulier, la version Lucid Synchrone fait usage de variants polymorphes, qui sont assez coûteux, pour représenter certaines valeurs. Elle génère également à chaque instant plusieurs références vers des n-uplets, ce qui a pour conséquence de remplir assez rapidement le tas de la machine virtuelle. Ainsi, le fait que le programme Lucid Synchrone nécessite d’augmenter la taille du tas, et de doubler la taille de la pile est aussi assez pénalisant pour une utilisation sur des microcontrôleurs dont les ressources mémoires sont limitées à quelques kilo-octets.

Durée d’exécution Vitesse Nombre Taille en Taille en

Nom (secondes) (milliers d’instr./seconde) de GC flash(octets) RAM(octets)

adder.ls 2.181 88.827 482 9250 1627

adder.ml 0.554 148.185 120 7552 1609

adder-na.ml 0.366 221.453 0 8472 1613

Table 7.9 – Vitesses et tailles des programmes Lucid Synchrone et OCaLustre (Atmega2560)

Options d’OMicroB : -arch 16 -gc SC -stack-size 150 -heap-size 600

7.2. Mesures de performances d’OCaLustre 185

let twobits_adder _cl147 (_148__c0, _149__a0, _150__a1, _151__b0, _152__b1) _325 _self_364 =

let _self_364 = match !_self_364 with

| ‘St_369(_self_364) -> _self_364 | _ -> (let _368 = {_366 = ref ‘Snil_;

_365 = ref ‘Snil_; _init329 = true} in

_self_364 := ‘St_369(_368); _368) in

let _cl339__ = ref false in let _338 = ref (false, false) in let _cl337__ = ref false in let _328 = ref false in

let _340 = ref (false, false) in

(if _cl147 then

(_328 := (or) _325 _self_364._init329; _cl337__ := true;

_338 := fulladder !_cl337__ (_149__a0, _151__b0, _148__c0) !_328 _self_364._365)); (let (_153__s0, _154__c1) = !_338 in

(if _cl147 then

(_cl339__ := true;

_340 := fulladder !_cl339__ (_150__a1, _152__b1, _154__c1) !_328 _self_364._366)); (let (_155__s1, _156__c2) = !_340 in

_self_364._init329 ← (&) !_328 (not _cl147); (_153__s0, _155__s1, _156__c2)))

let twobits_adder () =

let fulladder1_app = fulladder () in let fulladder2_app = fulladder () in fun (c0, a0, a1, b0, b1) ->

let (s0, c1) = fulladder1_app (a0, b0, c0) in let (s1, c2) = fulladder2_app (a1, b1, c1) in

(s0, s1, c2)

Figure 7.6 – Codes OCaml du nœud twobits_adder générés par Lucid Synchrone (haut) et par OCaLustre (bas)

Mesures sur les exemples du manuscrit: Le tableau 7.10 représente l’exécution de programmes OCa-Lustre issus de la plupart des exemples abordés dans ce manuscrit. Le nom de chaque programme correspond au nom de son nœud principal, qui est exécuté un million de fois. Les mesures réalisées valident les performances de vitesse mesurées sur l’additionneur, et la faible consommation mémoire d’OCaLustre. La consommation des ressources matérielles par l’extension OCaLustre est faible : son empreinte mémoire flash est comparable à celles d’autres programmes OCaml, et son empreinte en RAM est également basse. Cette utilisation parcimonieuse des ressources rend OCaLustre compatible avec de nombreux modèles de microcontrôleurs, possédant des capacités mémoires de l’ordre de 2 kilo-octets. Ainsi, OCaLustre apporte un modèle de programmation de haut niveau qui permet de réaliser des pro-grammes synchrones apportant de nombreuses garanties (que ce soit sur le plan du typage – d’horloges, ou des données manipulées – ou de la détection d’incohérences causales au sein des programmes). Ces avantages sont d’autant plus importants qu’ils n’entraînent pas d’augmentation de la consommation en ressources, et permet ainsi le développement de systèmes embarqués reposant sur du matériel aux capacités limitées.

Durée d’exécution Durée d’exécution Ratio Vitesse d’exécution Nombre de

Nom avec ocamlrun avec OMicroB avec OMicroB GC avec

(secondes) (secondes) (millions d’instr. OMicroB

bytecode/seconde) arith 0.85 1.54 1.81 337.76 357142 blinker 0.02 0.04 2.00 521.67 0 call_cpt 0.06 0.11 1.83 488.14 19058 cpt 0.01 0.03 3.00 645.06 0 ex_const 0.04 0.09 2.25 518.05 17721 ex_norm 0.03 0.06 2.00 575.05 0 ex_tuples 0.06 0.11 1.83 396.31 37411 fibonacci 0.02 0.04 2.00 635.31 0 fibonacci2 0.02 0.04 2.00 660.56 0 merge 0.03 0.06 2.00 558.22 0 ordo 0.06 0.13 2.16 397.50 37411 two_cpt 0.03 0.08 2.66 481.79 18705 watch 0.06 0.13 2.16 428.58 40404 when 0.06 0.10 1.66 395.53 18365 whennot 0.03 0.05 1.66 548.65 0

Table 7.10 – Mesures de performances de programmes OCaLustre (sur PC)

7.2. Mesures de performances d’OCaLustre 187

Conclusion du chapitre

Les différentes mesures réalisées dans ce chapitre valident le fait que notre approche reposant sur l’utilisation d’une machine virtuelle est viable et adaptée pour la programmation de microcontrôleurs dans un langage de haut niveau. Le simple fait que des programmes OCaml puissent être exécutés, à l’aide d’un interprète générique, sur du matériel avec de si faibles ressources, représente une réussite. De plus, les performances de la machine virtuelle OMicroB et de l’extension synchrone OCaLustre sont tout à fait correctes du point de vue de leur vitesse d’exécution, ainsi que de celui de la consommation mémoire des programmes générés : des applications non triviales peuvent être envisagées, pour une exécution sur du matériel très fortement limité en mémoire. Nous présentons ainsi dans le chapitre suivant quelques applications concrètes qui tirent profit d’OMicroB et d’OCaLustre.

189

8 Applications pour montages

électroniques

Nous présentons un ensemble d’applications pratiques ou ludiques réalisées avec les solutions lo-gicielles décrites dans ce manuscrit. Ces applications, pouvant être exécutées sur des microcontrôleurs à faibles quantités de ressources mémoires et computationnelles, témoignent de l’intérêt de l’approche haut niveau adoptée dans cette thèse. Il est en effet aisé de réaliser de petits programmes embarqués tirant profit des garanties apportées par le langage OCaml et la surcouche synchrone d’OCaLustre. Nous présentons trois exemples de programme, dont l’intérêt est de faire montre de l’expressivité de notre solu-tion, tout en validant le fait que cette dernière est compatible avec du matériel doté de faibles ressources. En particulier, les programmes réalisés peuvent être exécutés sur du matériel disposant de moins de 8 kilo-octets de RAM. Chaque programme décrit dans la suite est lié à un montage électronique concret. Le premier exemple est un programme permettant de contrôler un lecteur de cartes perforées simple, pour lequel chaque carte contient la représentation binaire d’un octet. Cet exemple témoigne en particulier de la correspondance entre la notion d’horloge synchrone et celle d’horloges « physiques », qui gouvernent la cadence d’arrivée des signaux électriques en entrée du programme. Le second programme régit le fonc-tionnement d’une tempéreuse à chocolat : un appareil de cuisine permettant de faire fondre du chocolat à une température précise. Le dernier exemple est l’implémentation d’un jeu vidéo de « Serpent » pour un

Arduboy, un petit appareil portatif dédié à l’exécution de jeux vidéo simples, classiquement réalisés en C.

L’ensemble des codes sources de ces programmes est disponible en annexe D.

8.1 Un lecteur de cartes perforées

Pour notre premier exemple, nous réalisons un programme pour microcontrôleur qui sera intégré à un montage représentant un lecteur de cartes perforées. Chacune de ces dernières contient la représen-tation d’un octet par l’intermédiaire de deux lignes horizontales contenant des trous. La première ligne représente le signal d’horloge : si le montage reconnaît un trou dans cette ligne, cela signifie qu’une donnée doit être également lue. Cette donnée est représentée sur la deuxième ligne de la carte : un trou représente la valeur binaire 1, tandis que l’absence de trou représente la valeur 0. La figure 8.1 est le schéma d’une carte perforée : cette carte correspond à l’octet représenté en binaire par la valeur 0b100010101.