I. Comment gagner en rapidité en optimisant la JVM

L'optimisation dynamique concerne tous les langages s'exécutant dans la JVM : Java, Scala, Groovy, Jruby, Closure, JavaScript, mais aussi tout ce qui passe par une création de classe java intermédiaire : JSP, XSD (via xjc), WDSL (via wsimport).

Pour que les méthodes de la JVM soient transformées en code assembleur machine par la JVM, il faut deux conditions essentielles :

  1. Un nombre d'exécutions de cette méthode qui doit être au moins de 1500 fois pour le mode -client ou de 10 000 fois pour le mode -server ;
  2. Une taille de méthode inférieure à 8 ko de bytecode (difficile de donner un équivalent en nombre de ligne de code). Une taille supérieure à 8 ko empêche toute compilation en assembleur de cette méthode.

À noter que l'optimisation du code par le compilateur JIT (Just In Time) et la traduction du bytecode en assembleur est d'autant plus grande si l'inlining est activé. L'inlining est la fusion d'une méthode appelante et appelée afin d'avoir une vision globale du traitement et d'en faciliter l'optimisation. Une méthode dépassant les 1000 octets de bytecode ne pourra pas profiter d'inlining d'inclusion. Elle sera donc moins rapide, même si une partie de son code sera transformée en assembleur. La JVM utilise différents types d'inlining, nous y reviendrons plus tard.

Par défaut, les JVM 32 bits activent le mode -client alors que les JVM 64 bits ne proposent que le mode -server. Selon les traitements, il y a un rapport de 1 à quasiment 800 entre le mode -client et -server pour la même JVM.

À noter que dans des calculs intensifs, un rapport de 1 à 4 peut aussi exister entre un JDK 32 bits et 64 bits.

II. Jusqu'à quel point le code assembleur généré est optimisé par rapport à un compilateur C

La transformation du bytecode en code assembleur machine apporte un réel gain de vitesse. Pour s'en convaincre, et pour l'exemple, le calcul d'une valeur de la suite de Fibonacci compilée en C avec l'optimisation agressive -O3 a un même temps de réponse moyen que la JVM 1.7 d'IBM utilisant un cache de méthodes déjà compilées (via -Xshareclasses). L'écart est seulement de 31 % avec le JDK 8 64 bits (sans option particulière au lancement de la JVM).

Version C
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
#include <stdio.h>
#include <time.h>

unsigned int fib(unsigned int n){
    unsigned int fibminus2 = 0;
    unsigned int fibminus1 = 1;
    unsigned int fib = 0;
    unsigned int i;

    if (n==0 || n==1) return n;
    for(i=2;i<=n;i++) {
        fib=fibminus1+fibminus2;
        fibminus2=fibminus1;
        fibminus1=fib;
    }

    return fib;
}

int main(int argc, char **argv) {
    unsigned int n;
    if (argc < 2) {
        printf(" usage: fib n\n"
            "Compute nth Fibonacci number\n");
        return 1;
    }

    clock_t t1, t2;
    t1 = clock();
    n = atoi(argv[1]);
    printf(" fib(%d) = %d\n ", n, fib(n));
    t2 = clock();
    int diff = (((float)t2 - (float)t1));
    printf(" time : %d ms ",diff);
    return 0;
}
Version Java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
class fib_nc {
    public static long fib(long n) {
        long fibminus2 = 0;
        long fibminus1 = 1;
        long fib = 0;
        long i;
        if (n==0 || n==1) return n;
        for(i=2;i<=n;i++){
            fib=fibminus1+fibminus2;
            fibminus2=fibminus1;
            fibminus1=fib;
        }
        return fib;
    }

    public static void main(String[] args) {
        long n;
        if (args.length < 1) {
            System.out.println(" usage: fib n\nCompute nth Fibonacci number\n ");
            return;
        }

        long t1, t2;
        t1 = System.currentTimeMillis();
        n = Long.valueOf(args[0]);
        System.out.printf(" fib(%d) iteratif = %d\n ", n, fib(n));
        t2 = System.currentTimeMillis();
        System.out.println(" time in ms :  " + (t2-t1));
    }
}

NB : la ligne de commande à exécuter pour compiler sous Mingw32 est la suivante sous Windows : gcc fib.c -o fib.exe -O3.

II-A. Du code source à l'optimisation dynamique

La JVM est une machine virtuelle multilangage et le nombre de langages supportés n'a pas cessé de grandir. Certains langages se limitent à de l'interprétation (même si elle est plus