Factorisation d’écriture dans les arrays pour optimiser la taille d’un Cardlet JavaCard

Il m’a été donné l’occasion de travailler sur une optimisation d’un Cardlet Sim Toolkit pour un opérateur télécom. Le problème principale était la taille de mémoire nécessaire pour installer l’Applet sur la carte. Il était donc question d’optimiser la taille du code.

Cette applet offrait un certain nombre de services par SMS, au bout du menu, de façon très classique, un SMS était construit et envoyé à un numéro précis pour fournir le service. Ci dessous un exemple du code utilisé pour constuire le SMS:

int index=0;
buffer[index++]=(byte)'A';
buffer[index++]=(byte)'B';
buffer[index++]=(byte)'C';
...

J’appellerai cela une écriture inline dans le buffer
Il se trouve que un des axes majeurs d’optimisation de cette applet était de transformer ce genre de pattern en :

index = addBytes(buffer,index,'A','B','C');
index = addBytes(buffer,index,...);
...

J’ai en effet créé une fonction statique addBytes surchargée en version à 3, 4 et 5 bytes que j’appelle en lieu et place de la définition manuelle des bytes.
Elle ajoute les parametres au buffer et retourne la nouvelle position d’index. J’appelerai ça une écriture factorisée dans le buffer.

Pourquoi est-ce une optimisation de la taille du code ?

Cela tien à la structure du code compilé java du .class (dit bytecode) que la JVM exécute. Comme la bonne vieille HP 48 GX le bytecode utilise une pile pour décrire à la JVM les paramètres des instructions à exécuter. Ainsi pour faire une addition de 1 + 1 le byte code sera :

  1. Pousser 1 dans la pile.
  2. Pousser 2 dans la pile.
  3. Additionner les deux au sommet de la pile.

Soit en byte code (en suppostant travailler avec des int) :

  1. iconst_1 (push constant value 1 into the stack)
  2. iconst_2 (push constant value 2 into the stack)
  3. iadd (add the two last values and store result into the stack)

La version avec les insersion dans l’array factorisés dans une methode statique est beaucoups moins gourmande en byte code.

Comparaison des deux approches : inline vs factorisée

Chaque ligne du type :
buffer[index++]=’A’
Reviens à faire les opérations suviante:

  1. Charge la référence de buffer dans la pile : Instruction aload
  2. Charge la valeur de index dans la pile : Instruction iload
  3. Charge la constant ‘A’ dans la pile : Instruction iconst
  4. Stock dans le buffer : Instruction bastore
  5. Incremente index : Instruction iinc

Faire cela 3 fois créer donc environ 15 insructions de bytecode.

Comparons cela avec la version factorisée :

index = addBytes(buffer,index,'A','B','C') + index;

Les instructions sont les suviantes :

  1. Charge la référence de buffer dans la pile : Instruction aload
  2. Charge la valeur de index dans la pile : Instruction iload
  3. Charge la constant ‘A’ dans la pile : Instruction iconst
  4. Charge la constant ‘B’ dans la pile : Instruction iconst
  5. Charge la constant ‘C’ dans la pile : Instruction iconst
  6. Appeler addBytes : Instruction invokestatic
  7. Ecriture de la valeur de retour dans index : Instruction istore

En bout de compte la version factorisée consomme 7 instructions de bytecode.

Comparaison des deux approches :

L’approche inline consomme 5 instructions par byte écris dans l’array (soit 15 instructions pour 3 bytes). L’approche factorisée en écrivant 3 bytes d’un coups consomme 7 instructions pour les trois bytes, donc en fonction du nombre d’écritures faites dans le code le gain est de l’ordre de 50% :

Nombre de bytes écris Nombre d’instructions inline Nombre d’instructions fatorisé Gain
3 15 7 53%
6 30 14 53%
60 300 140 53%

Avec une versions d’addBytes qui écris 5 bytes en un seul appel le gain devient de l’ordre de 60%.

Conclusion

Ainsi une connaissance plus fine du fonctionnement du bytecode permet d’optimiser la taille d’un code JavaCard, ceci dit, comme toujours lors de l’optimisation cela reste un tradeoff entre la performance et la taille : la version factorisée nécessite un invokestatic qui est plus gourmand en performance que une simple instruction astore.

Quelques références utiles :

  1. Specifications de la JVM
  2. Liste des instructions du bytecode java
  3. Intro au bytecode sur wikipediat