Les Green Patterns

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 :


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



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 coûteux 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. 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. 


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.


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.



Voici les différentes optimisations réalisées: 

Décodage 

Le traitement de décodage des QR Codes 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.


Deux 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.


Différentes optimisations ont également été 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.




On constate ensuite que les stratégies gagnantes pour limiter la consommation sont : 
  • Réaliser le maximum de traitements côté serveur quitte à réduire l’ergonomie à l’essentiel, 
  • 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.

Pour retrouver la vidéo de la restitution du Green Challenge lors de l’USI 2010, cliquez ici.