Catégorie : Bonnes Pratiques

Développer Green: les enseignements du Green Challenge

Le Green Challenge coorganisé par l’USI 2010 et par GreenIT.fr a réuni une quinzaine d’équipes d’avril à juin 2010. Son objectif: identifier des "greens patterns" de développement c’est-à-dire des bonnes pratiques logicielles pour réduire la consommation énergétique d’une application.

Rappel du périmètre du Challenge

Pour identifier les "greens patterns", le challenge proposait de faire baisser la consommation sur un exemple d’application: QRDecode. L’objectif de l’application QRDecode est de décoder des codes barre à deux dimensions (des "QR Codes"), d’afficher les coordonnées des contacts auxquels correspondent ces codes barres et de positionner ces contacts sur une carte. Une implémentation de référence était proposée, en voici une capture d’écran.
7avoir_greenc1.png
Schématiquement, si l’on se limite aux flux, le rôle de cette application est de transformer plusieurs dizaines de QR Codes représentés par des fichiers .QRC transmis à l’application en:

  • Des cartes de visites,
  • Des images (la représentation visuelle du QR Code),
  • Des coordonnées GPS,
  • Une carte affichant ces coordonnées.

7avoir_greenc2.jpg
Les différentes fonctionnalités de l’application QR Decode sont donc:

  1. Le décodage des fichiers QR Code en une représentation carte de visite (en l’occurrence VCard),
  2. La transformation des QR Codes en images,
  3. La géolocalisation des adresses contenues dans les VCard pour obtenir des coordonnées GPS,
  4. Le positionnement des points GPS sur une carte graphique,
  5. L’affichage des cartes de visites et de la carte graphique.

L’implémentation de référence de l’application QR Decode a été réalisée en Java et se décompose en deux parties: une partie serveur qui s’exécute sur Google App Engine et une partie client qui s’exécute sur le navigateur. Les deux parties de l’application sont instrumentées pour récupérer le temps CPU consommé. Pour la partie serveur cela se réalise via des APIs spécifiques fournis avec le projet, pour la partie client cela se réalise par l’intermédiaire d’un plug-in FireFox développé pour l’occasion, GreenFox.
7avoir_greenc3.png
Les équipes étaient jugées en fonction de la réduction de la consommation qu’elles apportaient à l’application de référence. Les paragraphes suivant décrivent les méthodes mises en œuvre par les différents participants.

Répartition des traitements

La bonne répartition des traitements entre le serveur et le client est un des éléments majeurs d’optimisation des performances énergétiques de l’application. En effet, le code qui s’exécute côté serveur chez Google dispose de conditions énergétiques idéales (une plateforme hautement mutualisée, un PUE optimisé) alors que le code qui s’exécute sur la machine cliente, même dans les conditions minimales que nous avions choisi (un netbook de type Asus EeePC) reste celui d’un PC individuel avec une importante déperdition d’énergie.
Dans l’implémentation de référence, les traitements se répartissaient comme suit:

7avoir_greenc4.jpg

La première optimisation consistait donc à décaler le plus possible de traitements côté serveur. Voici la répartition "idéale" proposée par les deux premières équipes sur le podium.

7avoir_greenc5.jpg

La géolocalisation est effectuée par appel d’un traitement Google. Néanmoins le fait de le réaliser via un appel Javascript et nettement plus couteux que lorsqu’il est intégré dans le code serveur. De plus, il est effectué pour chaque adresse ce qui est pénalisant. Une optimisation consiste à réaliser un appel batch sur le serveur pour réaliser la totalité du traitement en un seul appel. 7avoir_greenc6.png C’est possible via l’API Google mais cela impose des limites en nombre de requêtes lancées, un des gagnants a donc choisi d’utiliser MapQuest à la place.

Le positionnement des points sur la carte est également réalisé dans l’application de référence par l’API Javascript de GoogleMap. C’est encore une fois très couteux en CPU. Deux stratégies ont été utilisées pour éviter cet écueil. Dans les deux cas il s’agissait de supprimer les appels JavaScript. La première stratégie consiste à réaliser une génération complète sur le serveur, cela est possible en utilisant la version statique de Google Map qui génère une seule image intégrant la carte et tous les points.

7avoir_greenc7.png

Une autre stratégie était l’utilisation d’un Canvas HTML 5 dans la page avec un positionnement des points en relatif sur un simple fond de carte.

7avoir_greenc8.png

Dans les deux cas cela limite les fonctionnalités de l’application ce qui était autorisé par le règlement.

L’affichage se fait dans le navigateur et est donc obligatoirement côté client. Néanmoins, la plupart des candidats ont choisi d’alléger les traitements d’affichage en préparant le travail côté serveur. Ainsi, deux des équipes gagnantes ont directement encodées les images dans la page HTML (soit en base64 soit en passant par le data URI Scheme). Voir l’exemple ci-dessous.


<img height="50" width="50" src="data:image/png;
base64,iVBORw0KGgoAAAANSUhEUgAAADkAAAA5AQAAAACkY74o
AAACDElEQVR42gEBAv79AP////////+AAP////////+AAP/////
///+AAP///////+AAPAYd7eONAeAAPfbqk9pxfeAAPRQfik95Re
AAPRUm7PMLReAAPRXuYGD/ReAAPfVRdy43feAAPAVVVVVVAeAAP
/0Zp05D+AAPQawgCpNB+AAPPkxdcGbK+AAP4JncYKlFeAAPNm7P
7VX3eAAPTBYYKMZ9+AAPm+0JffuReAAPWb9RElkEeAAPl0j/6V3
X+AAPpDjnDKRfeAAPP21YMOLO+AAP/ZrsBga3eAAPp1YvR7JH+A
APedkyWKfbeAAPjzIuJHeQ+AAPIDg0C3cCeAAPRxGt1LRz+AAPJ
XDZTvJU+AAPJwKt0HZ3+AAPsE9cBTgDeAAPdmSEZypH+AAPRNEJ
WpD5+AAPfwhKWPv/eAAPfFOPoUyOeAAP5v84t9lfeAAPSZk6KAX
+AAP4j66af7u+AAPyOyI79UNeAAPy0CyhjnW+AAPfd4mu5JyeAA
PonjJeeab+AAPuASpOR/aeAAPjynbVmw6eAAPHMC4C7IEeAAPzp
NxW92+AAPAaVJQ/FXeAAPfS5pxXZ3eAAPRSTcGqIA+AAPRQHJKP
v6eAAPRV9gPpnN+AAPfY+dJQcPeAAPATFiaedyeAAP////////+
AAP////////+AAP//////+Ab4EaBY4WFZcAAAAASUVORK5CYII="
class="flashcode">

L’affichage HTML des cartes de visites a également été généré côté serveur ce qui permet d’éliminer le JavaScript qui génère le code HTML à partir de la représentation mémoire/JSON des données.

Optimisation des traitements

La plupart des équipes ont également travaillés sur l’optimisation des traitements côté serveur. Dans un premier temps, l’idée était d’effectuer un profiling de l’application afin d’identifier les lignes de code sur lequel le processeur passe le plus de temps. Plusieurs outils Java ont été utilisés pour cela: Netbeans Profiler, Java VisualVM ou JavaProfiler.
7avoir_greenc9.png
Voici les différentes optimisations réalisées:

Décodage

Le traitement de décodage des QR Code est clairement le traitement le plus coûteux en temps processeur sur la partie serveur.
Dans l’implémentation de référence, le traitement s’appuyait sur la librairie Open Source de Yusuke Yanbe.
7avoir_greencA.png
7avoir_greencB.pngDeux des équipes sur le podium ont fait le choix de chercher et de benchmarker une autre librairie. Ils se sont donc appuyés sur la librairie Zebra Crossing qui offre plus de fonctionnalités (support d’un plus grand nombre de type de code barre) et qui est surtout beaucoup plus performante que la librairie de départ.

Une autre stratégie, plus complexe, consistait à optimiser la librairie existante. Pour cela un profiling plus fin et des optimisations sur le code Java ont été mises en œuvres. En particulier:

  • Eviter les copies de blocs mémoires (voir exemple de code ci-dessous),
  • Limiter le nombre d’objets à instancier et favoriser la réutilisation des instances,
  • Passer en variable Static des tableaux de valeurs,
  • Limiter les conversions de types,
  • Limiter l’utilisation d’objet nécessitant de la synchronisation (ThreadSafe).

A noter que l’objectif de ces optimisations n’est pas de limiter l’usage mémoire (peu impactant énergétiquement) mais de limiter le nombre d’opérations pour alléger la CPU.

int[][] imageToIntArray(QRCodeImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[][] intImage = new int[width][height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
intImage[x][y] = image.getPixel(x, y);
}
}
return intImage;
}

Génération image

Un autre traitement coûteux était la génération de l’image graphique du QR Code à partir de l’adresse.
Une implémentation était fournie dans l’application de référence. Deux équipes ont fait le choix de la substituer par un appel à une API GoogleChart qui propose cette fonctionnalité. Cela permet en un simple appel HTTP de disposer de l’image.

<img height="50" width="50" src="http://chart.apis.google.com/chart?chs=228x228
&cht=qr&choe=UTF-8&chl=BEGIN%3AVCARD%0D%0AN%3AChambers%3BJohn+T.%0D%0ATEL%3BCELL
%3A%28408%29+524-7209%0D%0AADR%3BWORK%3A%3B
%3B170+W.+Tasman+Dr.%3BCA%3BSan+Jose%3B95134%3BUSA
%0D%0AORG%3ACisco+Systems%3B%0D%0AEND%3AVCARD" class="logo flashcode">

Même si cette option génère une économie énergétique, le jury a pris la décision de pénaliser ces deux équipes car l’appel à ce traitement n’est pas visible à travers nos instruments de mesures et pénalisait donc les autres équipes.
La plupart des autres équipes ont réalisés des optimisations sur le code de génération des images. L’optimisation la plus simple était de "rogner" la taille pour éviter le traitement de pixels inutiles.
7avoir_greencC.png
Différentes optimisations ont également étaient réalisées sur le traitement pour éviter de manipuler des pixels de couleurs alors que l’image du QR Code est nécessairement en noir et blanc. Enfin, l’encodage de l’image en bitmap a été privilégié en évitant de passer par les APIs AWT qui sont peu performantes.

Affichage

L’optimisation de l’affichage consistait d’abord à éviter l’utilisation du JSP qui provoque un overhead surtout au premier chargement. La plupart des équipes ont également pris la décision de construire le code HTML directement en utilisant des StringBuilder (voir exemple ci-dessous).

public String decode(StringBuilder sbCards, StringBuilder sbAlerts,
StringBuilder sbImages, File file, int id) throws Throwable {
...
// HTML of vcard
sbCards.append("<li class = \"vcard\">");
// preparing the HTML5 canvas
sbCards.append("<canvas id=\"canvas");
sbCards.append(id);
sbCards.append("\" width=\"");
sbCards.append(wOut);
sbCards.append("\" height=\"");
sbCards.append(hOut);
sbCards.append("\"></canvas>");
sbCards.append("<span class=\"name\">");
sbCards.append(sName);
sbCards.append("</span>");
sbCards.append("<span class=\"orga\">");
sbCards.append(sOrga);
sbCards.append("</span>");
sbCards.append("<span class=\"addr\">");
sbCards.append(sAddress);
sbCards.append("</span>");
sbCards.append("</li>'+\r\n'");
...

La plupart des équipes ont aussi fait en sorte d’éviter les aller/retours qui nécessitent des traitements de connexions côté client et côté serveur. Une des équipes a fusionné dans la page HTML: le code HTML, la feuille CSS, les images et le Javascript pour limiter les échanges à un seul aller/retour.
Le code HTML a été optimisé (suppression des espacements inutiles) par plusieurs équipes. Le code JavaScript a également était optimisé pour limiter le nombre d’appel AJAX qui impliquent des traitements (et donc de la CPU) pour réaliser les connexions. Le JavaScript a aussi été offusqué pour limiter le parsing. La meilleure stratégie était néanmoins d’éviter complétement le JavaScript !

Autres optimisations

D’autres optimisations ont été mise en œuvre. La principale concerne la gestion des traces et du code de débogage. L’idée étant de réaliser ces traitements de manière conditionnelle pour ne pas consommer du temps d’exécution inutile. Par exemple le code de trace:

canvas.println("Adjust AP(" + x + ","+ y+") to d("+dx+","+dy+")");

est remplacé par:

if (canvas.isPrintlnEnabled())
canvas.println("Adjust AP({},{}) to d({},{})", x, y, dx, dy);

Enfin, plusieurs équipes ont mis en œuvre les options de gestion du cache côté navigateur qui peuvent être intéressantes si l’application s’exécute plusieurs fois. Néanmoins le jury partait systématiquement d’un cache vide avant chaque mesure.

Quels enseignements ?

Le premier enseignement que l’on peut tirer de ce challanger est que l’optimisation énergétique d’une application est une réalité. Les gains obtenus entre l’application de référence et les équipes gagnantes vont de 20% pour la partie serveur à un gain de plus de 600% sur la partie cliente.
7avoir_greencD.png
7avoir_greencE.png
On constate ensuite que les stratégiques gagnantes pour limiter la consommation sont:

  • Réaliser le maximum de traitements côté serveur quitte à réduire l’ergonomie à l’essentiel côté client,
  • Profiler l’application pour identifier les traitements fortement consommateur de CPU,
  • Optimiser ces traitements pour limiter le nombre d’opérations réalisées ou remplacer ces traitements par des librairies plus efficaces,
  • Pré-générer les affichages sur le serveur et éviter le code interprété (JavaScript ou JSP).

C’est à ce prix que l’on aura des applications plus "green" mais aussi, globalement, de meilleure qualité.
Un grand merci à tous les participants qui nous permettent d’appréhender ces bonnes pratiques.
En cliquant sur le lien suivant vous pouvez retrouver la vidéo de la restitution du Green Challenge au cours de l’USI.

 

Green Challenge : sauvez les patterns !
Green Challenge : sauvez les patterns !

USI 2010 : conférence incontournable du l’IT en France
Rendez-vous annuel des Geeks et des Boss souhaitant une informatique qui transforme nos sociétés, USI est une conférence de 2 jours sur les sujets IT : Architecture de SI, Cloud Computing, iPhone, Agile, Lean management, Java, .net… USI 2010 a rassemblé 500 personnes autour d’un programme en 4 thèmes : Innovant, Durable, Ouvert et Valeur.
Plus d’informations sur www.universite-du-si.com

Lionel Laské

Diplômé d’un MasterPro Système et Communication Homme Machine de l’Université Orsay Paris XI, Lionel travaille depuis 15 ans chez C2S la SSII interne de Bouygues ou il a occupé les postes d’Ingénieur Consultant, Chef de projets, Responsable d’une équipe de développements et aujourd’hui, Directeur Innovation. Lionel anime également un groupe de travail Green IT regroupant les différents entités de la filière informatique du groupe Bouygues. Passionné par les nouvelles technologies, Lionel est architecte .NET et contributeur régulier des sites sur DotNetGuru, CodeProject et TechHead Brothers”. Lionel est par ailleurs Président et co-fondateur de OLPC France une association à but non lucratif qui pour objectif de diffuser l’esprit du projet One Laptop Per Child en France.

Site web