• Aucun résultat trouvé

CHAPITRE III APPROCHE D’INTÉGRITÉ BOUT EN BOUT BASÉE SUR LES CODES

III.3 É VALUATIONS POUR L ’ APPROCHE MONO CODE

III.3.3 Évaluations du coût de calcul des codes présélectionnés

III.3.3.3 Fonction de mesure des temps d’exécution, métriques et scénarios ciblés

mesures disponibles dans l’environnement de simulation et des plateformes sur lesquelles se déroulent les exécutions. Notamment, les fonctions de base ne permettent pas toujours d’avoir la précision souhaitée. De plus, il faut souvent instrumenter le programme de simulation, ce qui veut dire rajouter des instructions, qui viennent donc augmenter le temps d’exécution que l’on veut justement évaluer. Du fait de toutes ces difficultés, nous présentons dans le détail les différentes étapes qui ont conduit à la solution que nous avons utilisée pour mesurer le temps, en précisant que nous cherchons autant que possible à accéder à des mesures de temps réel (c’est-à-dire avec la plus petite granularité de temps possible).

1) Fonctions clock() et times()

Lorsque nous avons commencé nos expérimentations, nous ne dispositions pas encore d’une plateforme qui serait représentative de celle d’un système embarqué. Nous avons donc cherché à faire une première mesure des tests d’exécution des implémentations des codes CRC sur un poste de travail standard (un ordinateur de bureau). Une telle approche pose quelques problèmes pour obtenir des valeurs représentatives d’un cas réel, problèmes qui sont dus au contexte du système d’exploitation qui est un système multitâches à temps partagé. Les processeurs présents dans ce type de machine disposent généralement des mécanismes

III.3 Évaluations pour l’approche mono-code 67

d’accélération (caches, prédicteurs de branchement, etc.) qui ne sont pas forcément présents dans des processeurs embarqués et qui peuvent favoriser certains algorithmes par rapport à d’autres.

Dans un premier temps, pour essayer de disposer d’une grande portabilité, nous avons cherché à utiliser la fonction clock() qui fait partie de la bibliothèque standard « time.h » du C. Les temps étant fournis avec une granularité de 1 ms, il est nécessaire d’exécuter un grand nombre de fois le code pour arriver à avoir une valeur suffisamment précise. En effet, exécuter un grand nombre de fois un programme permet d’atténuer (de lisser) les fluctuations existantes entre deux exécutions sous un système d’exploitation standard. Mais en contrepartie, l’exécution répétée d’un même programme met en jeu des mécanismes d’accélération du processeur (dont notamment l’utilisation de cache), qui a pour effet de réduire les durées de certaines exécutions, et donc, au final de donner des temps d’exécution insuffisamment représentatifs d’un contexte embarqué, qui ne bénéficierait pas de ces mécanismes.

Conscients des problèmes évoqués, nous avons exploré d’autres pistes. Dans le programme de test de performance de l’outil PyCRC, l’auteur utilise la fonction times(). Mais le principe de cette fonction se heurte aux mêmes difficultés que la méthode précédente.

2) Fonction et stratégie de mesure

Faute d’avoir une fonction de mesure adéquate, nous avons cherché à comprendre le principe des mesures effectuées dans l’environnement « UniversalCRC ». Pour cela, nous avons pris directement contact avec son auteur qui nous a fourni son programme de mesure de temps. Le principe de mesure élémentaire repose sur l’instruction RDTSC des processeurs x86. Ce principe a le gros avantage de donner une précision égale au cycle horloge du processeur, donc une meilleure précision que les fonctions précédemment évoquées. Mais il a aussi l’inconvénient de ne pas être portable puisqu’il s’appuie sur une instruction spécifique d’une famille donnée de processeur, et donc, il faut adapter le code lorsqu’on change de processeur. Cependant, cet inconvénient est mineur au regard de la précision que l’on peut avoir avec cette solution, que nous avons donc décidé d’utiliser pour une solution basée sur l’utilisation de l’instruction RDTSC.

À titre d’exemple, la Figure III.7 donne le code des fonctions de l’auteur de « Universal CRC que nous avons retenu pour nos mesures actuelles. C’est le code élémentaire d’utilisation de l’instruction RDTSC. Il reste à voir plus précisément, comment l’utiliser pour fournir au final des temps d’exécution, c’est-à-dire définir la stratégie ou la politique pour exploiter les temps mesurés et en exploiter les plus significatifs.

Pour la stratégie de mesure qui consiste en l’exploitation des mesures obtenues (ou la stratégie de mesure), nous avons identifié deux stratégies différentes. La première stratégie est présentée dans un article d’Intel. Il s’agit d’un article [Gopal et al. 2012] qui s’intéresse plus précisément à la performance de l’instruction « CRC32 » présente dans les processeurs Core i7. La deuxième stratégie est celle adoptée par l’auteur de « Universal CRC ». À préciser que les deux stratégies sont basées sur l’instruction RDTSC des processeurs x86.

// fonctions mesures fournies par Danjel McGougan // * Copyright (C) 2011 Danjel McGougan

// * Contact me at <danjel.mcgougan@gmail.com>

/*

* Serialize the CPU (wait for all outstanding ops to retire) */

static inline void serialize() {

asm volatile ("xor %%eax, %%eax\n" "cpuid\n" : : :

"%eax", "%ebx", "%ecx", "%edx"); }

/*

* Read the time stamp counter of the CPU */

static inline unsigned __tsc() {

uint32_t tmp;

asm volatile ("rdtsc\n" : "=a" (tmp) : : "%edx"); return (unsigned)tmp;

}

/*

* Read the time stamp counter with serialization before and after */ unsigned tsc() { unsigned tmp; serialize(); tmp = tsc(); serialize(); return tmp; }

Figure III.7 : Fonction de mesure du temps de calcul

La différence entre les deux stratégies peut se résumer comme suit :

• La stratégie retenue par l’auteur de « Universal CRC » consiste à extraire un temps minimal d’exécution. Plus précisément, le code du CRC est exécuté 32 fois pour être sûr que tout le programme soit d’abord placé dans les caches (si l’environnement matériel dispose de cache), et on retient alors la valeur minimale du temps (parmi les 32 exécutions).

• La stratégie d’Intel est de fournir un temps moyen : le code CRC est exécuté 256 fois et la mesure de temps fournie est la moyenne des 128 exécutions donnant des temps intermédiaires (les 64 valeurs les plus faibles et les 64 valeurs les plus élevées sont éliminées).

Pour résumer, nos mesures de temps de calcul sont basées sur l’instruction RDTSC (pour l’avantage de la meilleure précision), et notre stratégie pour exploiter les mesures est inspirée de la stratégie d’Intel, qui, à notre sens, est la plus susceptible de fournir des valeurs plus réalistes. Toutefois, il reste alors le souci des effets des caches du processeur sur les temps mesurés. Pour limiter ces effets, nous avons préféré utilisé une boucle externe au programme de mesure (la boucle est déportée dans un shell Unix) pour ne pas favoriser la mise en cache des programmes et des données.

Plus précisément, en termes d’unité, les temps moyen d’exécution que nous mesurons sont exprimés en « ticks », ce qui correspond à un nombre de cycles d’horloge processeur

III.3 Évaluations pour l’approche mono-code 69

(typiquement 1 tick = 1 cycle d’horloge). Cette unité a l’avantage de faire abstraction de la fréquence du processeur.

Nous avons maintenant décrit pratiquement tous les éléments de notre environnement d’évaluation. Il manque encore des précisions plus spécifiques aux évaluations réellement faites, et qui sont introduites dans les sections III.3.3.4 et III.3.3.5. Une partie des codes des programmes et des shells développés est fournie dans l’annexe 2 avec des explications supplémentaires sur leur utilisation.