• [^] # Re: Fin de la pureté de Java

    Posté par . En réponse à la dépêche Java 8 et NetBeans 8 sont disponibles. Évalué à 6.

    Heureusement, la JVM sait déjà faire ça bien sur sinon les codes numériques auraient des perfs très très très mauvaises...

    Non la JVM ne sait pas faire ça. Tu n'as pas un système de type unifié et les deux mondes sont clos et ne communique que par un système de boxing couteux et dangereux. Les performances des Integer/Long sont désastreuses et leur occupation mémoire démentielle.

    On peut faire un benchmark très simple. On va faire la somme d'un tableau d'int puis d'Integer. La troisième variante c'est d'utiliser un Long au lieu d'un long pour maintenir la somme

    @OutputTimeUnit(TimeUnit.SECONDS)
    @State(Scope.Benchmark)
    public class MyBenchmark {
     static final int SIZE = 1_000_000;
     int[] intArray = IntStream.range(0, SIZE).toArray();
     Integer[] integerArray = IntStream.range(0, SIZE).boxed()
     .collect(Collectors.toList())
     .toArray(new Integer[SIZE]);
     @GenerateMicroBenchmark
     public long testPrimitive() {
     long sum = 0;
     for (int i = 0; i < intArray.length; i++) {
     sum += intArray[i];
     }
     return sum;
     }
     @GenerateMicroBenchmark
     public long testBoxed() {
     long sum = 0;
     for (int i = 0; i < integerArray.length; i++) {
     sum += integerArray[i];
     }
     return sum;
     }
     @GenerateMicroBenchmark
     public long testBoxed2() {
     Long sum = 0L;
     for (int i = 0; i < integerArray.length; i++) {
     sum += integerArray[i];
     }
     return sum;
     }
     public static void main(String[] args) throws RunnerException {
     Options opt = new OptionsBuilder()
     .include(".*" + MyBenchmark.class.getSimpleName() + ".*")
     .forks(1)
     .warmupIterations(5)
     .measurementIterations(5)
     .build();
     new Runner(opt).run();
     }

    Voilà le resultat

    Benchmark Mode Samples Mean Mean error Units
    o.s.MyBenchmark.testBoxed thrpt 5 430.597 15.005 ops/s
    o.s.MyBenchmark.testBoxed2 thrpt 5 120.147 8.491 ops/s
    o.s.MyBenchmark.testPrimitive thrpt 5 3341.320 502.704 ops/s
    

    C'est dix fois plus lent. Et encore dans ces microbenchmark tu ne paies pas le coup du GC qu'une vraie appli a. Entre testBoxed et testBoxed2 une majuscule de différence te rend presque encore 4x plus lent.

    Bref actuellement tu as le choix de faire plaisir au programmeur ou au programme... Sachant que ces problèmes remontent jusque dans les API du JDK. Tu as certaines choses de l'API de stream que tu ne peux pas faire pour des int par ce qu'il n'y a pas de type spécialisés.

    Certain langages sur la JVM ont un système de type unifié, que la JVM n'a pas, et ont un mécanisme de spécialisation pour essayer que ça ne soit pas trop désastreux en perf. Mais ça doit pas être si trivial par ce qu'on arrive très vite aux limites et tu repasses dans le monde slow-motion assez facilement.

    Bref si tu veux faire du Java performant et réussir à tenir plus de 4 entiers en mémoire tu restes sur les types primitifs et tu utilises les structures de données spécialisées trove par exemple. Oracle avait dans sa roadmap un système de type unifié pour Java 9 ou 10 mais pas vu une seule discussion depuis les talks de 2012. En attend pour tout ce qui est sérieux tu reste aux types primitifs et tu pleures sur l'API du JDK.

    D'un autre côté ca pousse très dur sur la perf en ce moment avec le retour en force des FFI, du off-the-heap, du code à la C par nos amis traders haute fréquence et base de données distribuées. Ça va être rigolo de voir comment ils vont s'en sortir chez OpenJDK :)