Licence CC BY

Java et Kotlin sont aussi rapides que C++ – tests à l'appui

Ou presque. Ça dépend des algos. Mais la JVM peut être plus rapide à algo identique !

Il y a quelques jours sur linuxfr.org, Julien Jorge a posté un journal intitulé « Exercices de programmation et benchmarks » dans lequel il fait divers tests de performances sur un exercice en C++, code à l’appui.

L’exercice est le suivant :

Étant donné une matrice d’entiers, l’exercice consiste à calculer la somme des nombres qui ne sont pas sous un zéro. Par exemple, pour

0, 2, 3
1, 0, 4
5, 6, 7

Le résultat attendu est 2 + 3 + 4 + 7 = 16. Le 1, le 5 et le 6 ne sont pas comptés car il y a un zéro plus haut dans la colonne.

La matrice est représentée en row-major, c’est-à-dire par une table de tables où chaque sous table représente une ligne de la matrice.

Julien Jorgue dans le billet LinuxFR d’origine.

C’est alors que je me suis fait les réflexions suivantes :

  1. Quelle seraient les performances des mêmes algorithmes, appliqués en Java et en Kotlin ?
  2. Y a-t-il une différence de performance entre Java et Kotlin ?
  3. Est-ce que les optimisations utilisées en C++ sont aussi efficaces en Java et Kotlin ?
  4. N’est-ce pas l’occasion de tester le framework de microbenchmark de Java ?

Et donc, je me suis mis à l’ouvrage.

Dans toute la suite du billet, je pars du principe que vous avez lu le journal d’origine !

Le Java Microbenchmark Harness (JMH) avec Gradle et IntelliJ

La JVM, ça apporte des comportements complexes, donc pour faire des benchmark sérieux, il faut l’outillage adapté.

Ici l’outil, c’est le Java Microbenchmark Harness (JMH), qui premet de « chauffer » la JVM et de lancer chaque test plusieurs fois, pour avoir des statistiques plus fiables et plus précises.

Ce qui n’empêche pas qu’il faut savoir interpréter les résultats, d’ailleurs l’outil le précise systématiquement à la fin des tests :

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell.

La configuration du JMH avec Gradle est facile. Si on fait les choses bien, on se retrouve avec une nouvelle racine de sources (src/jmh en plus de src/java et src/kotlin) dans lesquelles on peut mettre nos tests sans polluer le code métier.

En plus, IntelliJ reconnait automatiquement les tests, on peut les lancer directement sans avoir à jouer avec la ligne de commande :)

Par contre l’outil semble très fortement couplé à Java et à OpenJDK, je n’ai pas réussi à écrire de test en Kotlin (si tant est que ça ait un intérêt), et n’ai pas trouvé de moyen simple de les lancer sur une JVM tierce (typiquement celle d’IBM).

Des algorithmes par paquets de douze

Le choix des algorithmes

L’auteur d’origine a testé des tas d’algoritmes, et je n’allais pas m’amuser à tous les tester, parce que ça aurait été :

  1. Impossible pour certains qui utilisent des spécificités du langage1
  2. Beaucoup trop long à coder.
  3. Beaucoup trop long à tester (je reviendrai là-dessus dans la partie sur les chiffres).
  4. Sans grand intérêt.

J’ai sélectionné trois algorithmes :

  1. L’implémentation la plus efficace, donc de référence, en C++ sur CodeSignal dite « BestRatings »
  2. La première implémentation à faire mieux en C++ que l’implémentation de référence, dite « Branches »,
  3. Et l’implémentation la plus efficace pour les grosses matrices, dite « Indices ».

À noter que tous ces algorithmes travaillent sur des const std::vector<std::vector<int>>& matrix dont je ne sais pas s’ils sont plus proches d’une List<List<Integer>> matrix ou d’un int[][] matrix en Java, donc dans le doute j’ai fait les deux variantes à chaque fois. Et chaque algo est testé en version Kotlin et en version Java.

Ça fait donc 3 algos x 2 formats de données x 2 langages = 12 méthodes à tester…

Les codes présentés ci-dessous sont disponibles dans leur contexte sur Github : dans ce dépôt pour les codes JVM, et ici pour les codes C++. Ce qui devrait vous permettre de les lancer vous-même si vous connaissez les langages en question.

L’algorithme BestRatings

En version C++ :

int matrix_elements_sum_best_ratings(const std::vector<std::vector<int>>& m)
{
  int r(0);

  for (int j=0; j<(int)m[0].size(); j++)
    for (int i=0; i<(int)m.size(); i++)
      {
        if (m[i][j]==0)
          break;
        r += m[i][j];
      }

  return r;
}

L’algorithme est simplissime et direct, et pourtant très efficace.

Versions Java :

    public static int sum(List<List<Integer>> matrix) {
        int result = 0;

        for (int j  = 0; j < matrix.get(0).size(); j++) {
            for (int i  = 0; i < matrix.size(); i++) {
                if (matrix.get(i).get(j) == 0) {
                    break;
                }
                result += matrix.get(i).get(j);
            }
        }
        return result;
    }

    public static int sumArray(int[][] matrix) {
        int result = 0;

        for (int j = 0; j < matrix[0].length; j++) {
            for (int i = 0; i < matrix.length; i++) {
                if (matrix[i][j] == 0) {
                    break;
                }
                result += matrix[i][j];
            }
        }
        return result;
    }

On peut remplacer la boucle interne for (int i = 0… par un « for étendu » for (List<Integer> integers : matrix) { sans que ça ne change quoi que ce soit au niveau des performances.

Versions Kotlin :

    @JvmStatic
    fun sum(matrix: List<List<Int>>): Int {
        var result = 0
        for (j in matrix[0].indices) {
            for (i in matrix.indices) {
                if (matrix[i][j] == 0) {
                    break
                }
                result += matrix[i][j]
            }
        }
        return result
    }

    @JvmStatic
    fun sumArray(matrix: Array<IntArray>): Int {
        var result = 0
        for (j in matrix[0].indices) {
            for (i in matrix.indices) {
                if (matrix[i][j] == 0) {
                    break
                }
                result += matrix[i][j]
            }
        }
        return result
    }

On remarque que le code est strictement identique à l’exception de la signature de la méthode… et hélas impossible à factoriser, puisqu’il manque un dénominateur commun aux types List et Array en Kotlin :(

L’algorithme Branches

On maintient une liste des colonnes à ignorer pour éviter des tests si on a connaissance d’un 0 plus haut dans la colonne.

En version C++ :

int matrix_elements_sum_branches(const std::vector<std::vector<int>>& matrix)
{
  const int row_size(matrix[0].size());
  std::vector<char> usable_column(row_size, 1);

  int result(0);

  for (const std::vector<int>& row : matrix)
    for (int i(0); i != row_size; ++i)
      if (usable_column[i])
        {
          const int v(row[i]);

          if (v == 0)
            usable_column[i] = 0;
          else
            result += v;
      }

  return result;
}

En Java :

public static int sum(List<List<Integer>> matrix) {

        boolean[] usableColumns = new boolean[matrix.size()];
        Arrays.fill(usableColumns, true);
        int result = 0;

        for (List<Integer> row : matrix) {
            for (int i = 0; i != row.size(); i++) {
                if (usableColumns[i]) {
                    int v = row.get(i);
                    if (v == 0) {
                        usableColumns[i] = false;
                    } else {
                        result += v;
                    }
                }
            }
        }
        return result;
    }

    public static int sumArray(int[][] matrix) {

        boolean[] usableColumns = new boolean[matrix.length];
        Arrays.fill(usableColumns, true);
        int result = 0;

        for (int[] row : matrix) {
            for (int i = 0; i != row.length; i++) {
                if (usableColumns[i]) {
                    int v = row[i];
                    if (v == 0) {
                        usableColumns[i] = false;
                    } else {
                        result += v;
                    }
                }
            }
        }
        return result;
    }

Et en Kotlin :

    @JvmStatic
    fun sum(matrix: List<List<Int>>): Int {
        val usableColumns = BooleanArray(matrix.size)
        Arrays.fill(usableColumns, true)
        var result = 0
        for (row in matrix) {
            for (i in row.indices) {
                if (usableColumns[i]) {
                    val v = row[i]
                    if (v == 0) {
                        usableColumns[i] = false
                    } else {
                        result += v
                    }
                }
            }
        }
        return result
    }

    @JvmStatic
    fun sumArray(matrix: Array<IntArray>): Int {
        // [...] (code identique à la méthode précédente, mais non factorisable)
    }

L’algorithme Indices

L’inconvénient de l’algorithme précédent est qu’il nous oblige à parcourir les double boucles même quand on sait que lire certaines colonnes sont inutiles. On remplace donc notre référentiel de colonnes à tester par une liste de colonnes à tester, de façon à n’itérer que sur celles où c’est nécessaire.

En C++

int matrix_elements_sum_indices(const std::vector<std::vector<int>>& matrix)
{
  const int row_size(matrix[0].size());
  std::vector<int> usable_columns(row_size);

  const auto usable_columns_begin(usable_columns.begin());
  auto usable_columns_end(usable_columns.end());

  std::iota(usable_columns_begin, usable_columns_end, 0);

  int result(0);

  for (const std::vector<int>& row : matrix)
    for (auto it(usable_columns_begin); it != usable_columns_end;)
      {
        const int i(*it);
        const int v(row[i]);

        if (v == 0)
          {
            --usable_columns_end;
            *it = *usable_columns_end;
          }
        else
          {
            result += v;
            ++it;
          }
      }

  return result;
}

En Java

    public static int sum(List<List<Integer>> matrix) {

        List<Integer> usableColumns = IntStream.range(0, matrix.size()).boxed().collect(Collectors.toList());
        int result = 0;

        for (List<Integer> row : matrix) {
            for (int it = 0; it < usableColumns.size(); it++) {
                int i = usableColumns.get(it);
                int v = row.get(i);
                if (v == 0) {
                    usableColumns.remove(it);
                    it--;
                } else {
                    result += v;
                }
            }
        }
        return result;
    }

    public static int sumArray(int[][] matrix) {

        int[] usableColumns = IntStream.range(0, matrix.length).toArray();
        int result = 0;

        for (int[] row : matrix) {
            for (int it = 0; it < usableColumns.length; it++) {
                int i = usableColumns[it];
                int v = row[i];
                if (v == 0) {
                    usableColumns = ArrayUtils.remove(usableColumns, it);
                    it--;
                } else {
                    result += v;
                }
            }
        }
        return result;
    }

En Kotlin

    @JvmStatic
    fun sum(matrix: List<List<Int>>): Int {
        val usableColumns =
            IntStream.range(0, matrix.size).boxed().collect(Collectors.toList())
        var result = 0
        for (row in matrix) {
            var it = 0
            while (it < usableColumns.size) {
                val i = usableColumns[it]
                val v = row[i]
                if (v == 0) {
                    usableColumns.removeAt(it)
                    it--
                } else {
                    result += v
                }
                it++
            }
        }
        return result
    }

    @JvmStatic
    fun sumArray(matrix: Array<IntArray>): Int {
        var usableColumns = IntStream.range(0, matrix.size).toArray()
        var result = 0
        for (row in matrix) {
            var it = 0
            while (it < usableColumns.size) {
                val i = usableColumns[it]
                val v = row[i]
                if (v == 0) {
                    usableColumns = ArrayUtils.remove(usableColumns, it)
                    it--
                } else {
                    result += v
                }
                it++
            }
        }
        return result
    }

C’est l’algorithme dont l’écriture en Kotlin s’écarte le plus de la version Java.


  1. Par exemple le tout premier présenté contient une interprétation d’une inégalité comme un entier : usable_column[i] *= (v != 0), ce qui n’est pas possible en Java sans casser le concept voulu par l’auteur.

Des données de test

Pour que les tests soient compables, il me fallaient des données qui ressemblent au jeu de test d’origine. Or l’auteur ne travaille pas avec des données fixes, mais génère des matrices aléatoires, où la proportion d’avoir des 0 augmente avec la profondeur dans la matrice. Évidemment, les temps de génération de la matrice ne sont pas comptabilités dans les tests de performance. J’ai donc copié son algorithme :

std::vector<std::vector<int>> random_matrix(int s)
{
  static std::unordered_map<int, std::vector<std::vector<int>>> cache;
  
  boost::random::mt19937 gen(g_seed);
  boost::random::uniform_int_distribution<int> rg;
  
  const int row_size(s);
  const int row_count(s);
  const int max_value(100);

  std::vector<std::vector<int>> matrix(row_count);

  for (int y(0); y != row_count; ++y)
    {
      std::vector<int>& row(matrix[y]);
      row.resize(row_size);

      for (int x(0); x != row_size; ++x)
        if (rg(gen) % s >= y)
          row[x] = 1 + rg(gen) % (max_value - 1);
        else
          row[x] = 0;
    }
  
  return matrix;
}

Qui devient :

    List<List<Integer>> randomMatrix(int size) {
        Random rg = seed == null ? new Random() : new Random(seed);

        final int maxValue = 100;

        List<List<Integer>> matrix = new ArrayList<>(size);

        for (int y = 0; y != size; y++) {
            List<Integer> row = new ArrayList<>(size);

            for (int x = 0; x != size; x++) {
                row.add(x, rg.nextInt(size) >= y ? (1 + rg.nextInt(maxValue - 1)) : 0);
            }
            matrix.add(y, row);
        }

        cache.put(size, matrix);

        return matrix;
    }

Manipuler les nombres aléatoires me semble bien plus confortable en Java.

Des résultats !

Matériel de test

Je ne sais pas lancer le code C++ directement sur ma machine. Les résultats C++ ont donc directement été récupérés sur le journal d’origine !

Matériel de test pour le C++ :

Run on (4 X 3100 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x2)
L1 Instruction 32 KiB (x2)
L2 Unified 256 KiB (x2)
L3 Unified 3072 KiB (x1)

Matériel de test JVM (Java / Kotlin) :

Run on AMD Ryzen 5 3600X 6-Core Processor (12 X 3800 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x6)
L1 Instruction 32 KiB (x6)
L2 Unified 512 KiB (x6)
L3 Unified 16 MiB (x2)

Un petit tableau ?

Temps d’exécution moyen, en nanosecondes, en fonction de l’algorithme et de la taille de la matrice :

Taille 10 x 10 100 x 100 1000 x 1000 10000 x 10000
branches C++ 116 7 689 481 443 70 364 814
bestRatings C++ 42 1 091 41 886 3 454 125
bestRatings 54 1 802 70 637 2 991 120
bestRatingsKt 101 2 456 83 027 3 393 295
bestRatingsArray 31 717 29 160 1 265 142
bestRatingsArrayKt 30 784 26 995 1 448 296
branches 117 5 003 550 489 27 319 417
branchesKt 110 4 658 639 811 41 354 458
branchesArray 80 4 465 426 882 27 351 947
branchesArrayKt 73 4 803 444 350 38 913 710
indices 380 7 188 206 245 6 069 782
indicesKt 363 6 986 170 402 5 786 547
indicesArray 203 3 528 220 594 18 778 910
indicesArrayKt 204 3 620 234 809 18 853 039

Je n’ai hélas pas le chiffre pour l’algorithme indices en C++, l’auteur ne donne pas les valeurs avec les tailles en puissance de 10 pour cet algorithme.

Bon, on est d’accord, on ne voit pas grand-chose. On remarque néanmoins que :

  1. Les ordres de grandeur entre les résultats C++ et JVM (Java, Kotlin) sont les mêmes.
  2. Les versions Kotlin et Java ont des résultats identiques (à la variabilité des tests près).

On va essayer de rendre ça un peu plus clair…

Un graphique pour les étudier tous

Je relance donc mes benchmark, cette fois sur toutes les tailles de matrice qui sont des puissances entières de 2 entre 214 (soit 16 384)1. J’enlève les versions Kotlin, vu que les résultats sont identiques aux versions Java, pour garder un graphique lisible. Je récupère aussi les valeurs des mêmes algorithmes en C++ sur les mêmes tailles de matrice depuis le dépôt Git d’origine.

Ensuite, je fais la même chose que sur le journal d’origine : je normalise toutes les valeurs en prenant l’implémentation C++ « bestRatings » en référence, donc avec un temps d’exécution relatif qui vaut 1 pour toutes les tailles de matrices.

Et donc, on obtinent ce graphique, avec 3 couleurs pour les 3 types d’algorithmes.

Ce qu’on observe est si intéressant que je vais écrire une section complète dessus !


  1. Ce qui donne des grosses matrices pour les dernières tailles : une matrice d’entiers (int de 4 octets) de 16 384 * 16 384 prends exactement 1 Go de RAM pour les données seules !

Une analyse des résultats : 3 algos, 3 comportements différents

L’algorithme BestRatings (en bleu)

Tant qu’on est pas sur des matrices proprement gigantesques, la version JVM avec listes est légèrement plus lente et la version JVM avec tableaux est légèrement plus rapide que la version C++.

Dans tous les cas on est dans les mêmes ordres de grandeur (facteur environ 2–3 entre les versions les plus lentes et les plus rapides de l’algorithme).

L’algorithme Branches

Cet algorithme offre un résultat très curieux : les performances des deux implémentations JVM (listes et tableaux) sont pratiquement identiques… et systématiquement plus rapides que la version C++. On peut supposer que le compilateur JIT (Just-In-Time) de la JVM arrive particulièrement bien à optimiser ce code, par rapport à la version C++ native. On reste néanmoins dans les mêmes ordres de grandeur (facteur environ 2–3 entre les versions JVM et C++ de l’algorithme).

Ça serait intéressant si l’algorithme n’était pas aussi le plus lent dès que les matrices sont un peu grosses…

L’algorithme Indices

Ici, au contraire, les versions JVM sont systématiquement plus lentes que la version C++. Mais au contraire des deux autres où les versions à base de listes et de tableau avaient des comportements proches, ici les deux versions JVM ont des comportements différents au fur et à mesure que la taille des matrices grossit. C’est au point que la version JVM avec listes, qui est pour toutes les petites tailles l’algorithme le plus lent de tous, arrive à battre le BestRating C++ sur les très grosses matrice !

Les algorithmes entre eux

Contrairement au C++ où Indices battait BestRatings pour les très grosses matrices, aucun algorithme ne fait mieux que BestRatings en restant sur la JVM.
En première approche, ceci s’explique sans doute parce que le surcout des structures de contrôles est relativement élevé et n’est pas compensé par le gain en performances, contrairement au C++ qui permet de travailler au plus près du processeur.

On voit néanmoins que la JVM, grâce à son compilateur JIT, a des performances tout à fait honorables qui n’ont pas grand-chose à envier à du code natif en terme de puissance de calcul pure.

Enfin, une différence à priori mineure (un code implémenté avec des tableaux et non des ArrayList – qui stockent pourtant leurs données dans un tableau) peut apporter des différences de comportement significatives.

Une digression sur le Java Microbenchmark Harness (JMH)

Le Java Microbenchmark Harness (JMH) est très pratique, à commencer parce qu’il permet de faire des mesures de performances de fonction de manière stable et fiable. Ce qui en soit est compliqué avec une JVM, à cause de tous les mécanismes internes.

Le revers de la médaille, c’est que c’est long.

Le comportement standard du JMH, c’est de faire des run de 10 secondes pendant lesquelles il lance en boucle la fonction à tester.

Pour être sûr que le compilateur JIT est bien lancé, la fréquence processeur stabilisée et tous les caches en place, il commence par faire 5 run « à blanc », sans tenir compte des résultats.

Puis, pour avoir un résultat fiable, il refait 5 run, cette fois en collectant les mesures.

Ça veut dire 50 secondes d’exécution pure par méthode à tester, plus la collecte etc. Donc générer les 14 valeurs pour chacun des 6 algorithmes pour JVM du graphique ci-dessus, ça m’a pris plus d’une heure. Évidemment, comme c’est un test de performances, on ne lance pas les tests en parallèle sur plusieurs threads, ça irait plus vite mais fausserait complètement les résultats…


D’accord, le titre était un peu provocateur… cela dit, j’aurais pu soigneusement choisir mes résultats (l’algorithme Branches et l’implémentation en tableaux de l’algorithme BestRatings) pour clamer haut et fort que Java et Kotlin sont plus rapides que C++.

Ce qui aurait été mensonger et pas très intéressant, d’autant que ce petit test a mis en exergue des comportements curieux et notables. Voici ce que je dirais en conclusion :

  1. La JVM a de bonnes performances de calcul. On ne peut claiment plus dire que « Java c’est lent » sans passer pour un ignare qui méconnait son sujet. Évidemment, si votre besoin (et j’insiste, « besoin » et non « envie ») c’est la perfomance maximale, un langage plus proche de la machine est souhaitable ; si vous avez simplement besoin que ce soit raisonnablement performant, la JVM est là pour vous.
  2. Un algorithme n’est pas bon dans l’absolu. On l’a vu : le meilleur algorithme en C++ n’est pas le meilleur algorithme sur JVM. Testez et vérifiez, toujours !
  3. Le meilleur algorithme dépend de vos données. Quel que soit le langage, le meilleur algorithme pour traiter de petites matrices n’est pas le meilleur pour traiter de grosses matrices. Là encore, testez et vérifiez.

Et dans tous les cas, si vous avez réellement besoin de performances sur une section critique de votre programme, ne vous contentez pas simplement d’un bête microbenchmark ou d’un benchmark tout court ; comme le dit la sortie du JMH :

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell.

14 commentaires

Bonjour.

Je ne saisis pas trop l’intérêt de ne publier que des extraits épars du code impliqué dans le benchmark : si on veut le comprendre/l’interpréter, ce qui est en principe utile est de disposer du code entier sur un dépôt ou dans une archive, pour les deux langages (de quoi le réexécuter, et pouvoir comparer en détail les points d’entrée de ce qui est testé et/ou le code généré à plus ou moins bas niveau).

Ce qui est sûr, c’est qu’à instructions générées jusqu’au microprocesseur identiques, le code Java aura le léger overhead de la JIT en plus, mais là difficile de connaître le code généré pour analyser le soucis, vu qu’on n’a pas les points d’entrée du code ni les matériaux pour le comparer.

Un point qui m’a fait tilter et qui semble être seulement balayé rapidement dans l’article : la phase d’appel au PRNG est-elle faite à chaque itération du benchmark, et est-elle comptée dans les mesures, que ce soit en Java et en C++ ? Tu utilises le PRNG par défaut de Java (qui est très simple, presque autant que celui de libc) alors que le code C++ utilise un Mersenne Twister, qui est un algorithme plutôt lent dans sa catégorie.

Je pense qu’une analyse complète aurait bénéficié à inclure les représentations intermédiaires (bytecode JVM dans le cas de Java, bytecode LLVM de Clang dans le cas de C++) et l’assembleur généré dans les deux cas, pour une comparaison plus complète, en plus du code de build associé au banc d’essai (ces éléments ne sont pas trop difficiles à obtenir).

Bonne journée,


Edit : Je ne l’avais pas vu au premier abord, mais apparemment un dépôt est disponible en cliquant sur « configuration du JMH avec Gradle » (j’ai fait Ctrl+U et cherché une mention de Git pour le trouver) : https://github.com/SpaceFox/matrixsum

Et pour le code C++ d’origine : https://github.com/j-jorge/codesignal

Cela gagnerait à être mis en début et fin d’article je pense (un lien dans l’intro vers le dépôt produit + une liste de sources/bibliographie à la fin par exemple).

+4 -0

@r0anne tu as raison, je vais mieux indiquer les dépôts Git et le fait que la génération des matrices n’est pas testée dans le bench. Edit : c’est fait.

Pour le reste (représentations internes, biographie…) c’est un simple billet qui m’a déjà pris beaucoup plus de temps que ce que je m’étais alloué pour ce test. C’est sûr qu’aller voir l’assembleur généré serait intéressant, mais j’ai clairement pas le temps pour le faire (ni les compétences pour interpréter les assembleurs).

Si tu as l’occasion de le faire de ton côté, n’hésites surtout pas :)

Salut,

Tu ne montrerais pas plutôt que ta machine est plus rapide que la sienne ? Meilleure fréquence et plus gros caches, toutes chose égales par ailleurs tu auras de meilleures perf. Là tu changes deux choses, la machine et le langage. Difficile de conclure quoique ce soit…

+2 -0

@adri1 non, parce que les tests montrent bien ce que je résume en conclusion, et ne présentent pas de bizarrerie qui obligerait à aller chercher du côté de la différence entre les processeurs :

  1. Sur les performances de calcul, on est clairement dans le même ordre de grandeur. Et c’est tout : le but de ce billet n’était absolument pas de mesurer la différence de perfs entre une JVM et du C++1, mais bien de vérifier si on est, ou pas, dans le même ordre de grandeur.
  2. Sur le fait qu’un algorithme n’est pas bon dans l’absolu : là on lit le comportement général des algos, la remarque aurait été valable même s’il y avait eu une énorme différence de performances entre les deux systèmes.
  3. Sur le fait que le meilleur algo dépend des données, c’est vérifié indépendamment en prenant les seuls algos C++ ou JVM. Là encore, la différence de CPU n’entre pas en ligne de compte.

  1. Auquel cas il aurait effectivement fallu aller chercher l’assembleur généré et tout faire tourner sur la même machine.

le but de ce billet n’était absolument pas de mesurer la différence de perfs entre une JVM et du C++, mais bien de vérifier si on est, ou pas, dans le même ordre de grandeur.

Un test d’égalité, même à la louche, c’est une mesure de différence. Je serais vraiment curieux de voir ce que ça donne sur la même machine. D’expérience, les tailles de cache influencent beaucoup les temps quand on manipule des matrices. Faut juste installer une JVM maintenant…

Pour les points 2 et 3 oui, mais c’est pas ce que tu mets en avant dans le titre et c’est dommage.

+1 -0

Faut juste installer une JVM maintenant…

Ou si tu sais comment lancer les tests C++ et que tu le mets ici, je le fais de mon côté et mets le test à jour.

Pour les points 2 et 3 oui, mais c’est pas ce que tu mets en avant dans le titre et c’est dommage.

Il me paraissait clair que le titre n’était là que pour attirer le chaland :(

Il me paraissait clair que le titre n’était là que pour attirer le chaland

C’est probablement une déformation professionelle mêlée à ma haine viscérale des titres racoleurs qui me pousse à prendre les titres trop au sérieux. Ça reste un article intéressant, ne serait-ce que pour la comparaison d’algos. Je suis un peu surpris que Branches soit aussi mauvais que ça par rapport aux deux autres en C++ d’ailleurs.

+1 -0

J’ai fait un passage sur mes stats récemment, et je me demandais si un titre provocateur aiderait le contenu à se propager :)

PS : ma précédente propositions est authentique : je ne sais vraiment pas lancer ces foutus tests C++ (sous Linux x64).

ma précédente propositions est authentique : je ne sais vraiment pas lancer ces foutus tests C++ (sous Linux x64).

SpaceFox

Sous Fedora, y’a ces dépendances à installer, en plus de GCC :dnf install cmake google-benchmark-devel boost-devel. Les noms sont différents sous Debian/Ubuntu : ça a l’air d’être libbenchmark-dev et libboost-random-dev, mais je n’ai pas testé. Ensuite, c’est :

cmake .
make
./matrix-elements-sum-GNU

Pour lancer les benchmarks C++ du dépôt donné sous Linux, c’est assez simple : il te faut trois dépendences : CMake ≥ 3.13 (disponible avec Ubuntu 19.04 et plus récent, si tu as plus vieux installe depuis le site), Boost (dont libboost-dev-random) et libbenchmark (et un compilateur C++ bien sûr).

Si tu as une vieille libbenchmark, il va te falloir supprimer cette ligne du CMakeLists.txt :

  benchmark::benchmark_main

Ensuite, les commandes à taper seront simplement :

cmake .
make
./matrix-elements-sum-GNU

CMake est un utilitaire qui permet de générer des Makefiles « facilement », assez utilisé ces dernières années dans le monde C++, pour le contexte.

Chez moi, les résultats suivants sont produits :

$ time ./matrix-elements-sum-GNU 
Run on (4 X 3400 MHz CPU s)
2020-02-18 16:01:48
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
***WARNING*** Library was built as DEBUG. Timings may be affected.
----------------------------------------------------------
Benchmark                   Time           CPU Iterations
----------------------------------------------------------
indices/2                 253 ns        253 ns    2679437
indices/4                 390 ns        390 ns    1781599
indices/8                 916 ns        916 ns     713345
indices/16               2105 ns       2105 ns     336135
indices/32               4912 ns       4912 ns     131470
indices/64              11253 ns      11253 ns      59591
indices/128             30248 ns      30248 ns      22888
indices/256             85898 ns      85897 ns       7898
indices/512            228743 ns     228743 ns       2981
indices/1024           617549 ns     617534 ns       1158
indices/2048          1676668 ns    1676403 ns        423
indices/4096          5069406 ns    5069424 ns        133
indices/8192         14904924 ns   14900751 ns         47
indices/16384        43842610 ns   43839852 ns         16
best_ratings/2             67 ns         67 ns   10688495
best_ratings/4            133 ns        133 ns    5014556
best_ratings/8            545 ns        545 ns    1252762
best_ratings/16          1558 ns       1558 ns     435550
best_ratings/32          4035 ns       4035 ns     181212
best_ratings/64          9548 ns       9548 ns      73839
best_ratings/128        25452 ns      25452 ns      26844
best_ratings/256        75685 ns      75684 ns       9320
best_ratings/512       210935 ns     210936 ns       3354
best_ratings/1024      555353 ns     555357 ns       1202
best_ratings/2048     1528983 ns    1528458 ns        450
best_ratings/4096     4945722 ns    4945700 ns        136
best_ratings/8192    13090379 ns   13090079 ns         52
best_ratings/16384   38270184 ns   38270026 ns         17
column_major/2            120 ns        120 ns    5883681
column_major/4            217 ns        217 ns    3170373
column_major/8            675 ns        675 ns    1021136
column_major/16          1643 ns       1643 ns     412681
column_major/32          3949 ns       3949 ns     182039
column_major/64          8894 ns       8874 ns      72570
column_major/128        23344 ns      23270 ns      29013
column_major/256        68300 ns      68140 ns       9993
column_major/512       177348 ns     177272 ns       3966
column_major/1024      475957 ns     474692 ns       1504
column_major/2048     1280289 ns    1279348 ns        536
column_major/4096     3961745 ns    3956830 ns        191
column_major/8192    10501951 ns   10501841 ns         68
column_major/16384   29483796 ns   29483657 ns         22

real    1m34,256s
user    1m33,116s
sys 0m0,856s

$ cat /proc/cpuinfo 
processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 78
model name  : Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
stepping    : 3
microcode   : 0xd6
cpu MHz     : 800.002
cache size  : 4096 KB
physical id : 0
siblings    : 4
core id     : 0
cpu cores   : 2
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 22
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit
bogomips    : 5616.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:

processor   : 1
vendor_id   : GenuineIntel
cpu family  : 6
model       : 78
model name  : Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
stepping    : 3
microcode   : 0xd6
cpu MHz     : 800.008
cache size  : 4096 KB
physical id : 0
siblings    : 4
core id     : 1
cpu cores   : 2
apicid      : 2
initial apicid  : 2
fpu     : yes
fpu_exception   : yes
cpuid level : 22
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit
bogomips    : 5616.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:

processor   : 2
vendor_id   : GenuineIntel
cpu family  : 6
model       : 78
model name  : Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
stepping    : 3
microcode   : 0xd6
cpu MHz     : 800.000
cache size  : 4096 KB
physical id : 0
siblings    : 4
core id     : 0
cpu cores   : 2
apicid      : 1
initial apicid  : 1
fpu     : yes
fpu_exception   : yes
cpuid level : 22
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit
bogomips    : 5616.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:

processor   : 3
vendor_id   : GenuineIntel
cpu family  : 6
model       : 78
model name  : Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz
stepping    : 3
microcode   : 0xd6
cpu MHz     : 800.023
cache size  : 4096 KB
physical id : 0
siblings    : 4
core id     : 1
cpu cores   : 2
apicid      : 3
initial apicid  : 3
fpu     : yes
fpu_exception   : yes
cpuid level : 22
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx rdseed adx smap clflushopt intel_pt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp md_clear flush_l1d
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit
bogomips    : 5616.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:

Quand j’essaie de lancer tes tests avec Gradle en local, j’obtiens l’erreur suivante. Saurais-tu me dire pourquoi ?

$ time ./gradlew benchmark --stacktrace

FAILURE: Build failed with an exception.

* What went wrong:
Task 'benchmark' not found in root project 'matrixsum'.

* Try:
Run gradlew tasks to get a list of available tasks. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.execution.TaskSelectionException: Task 'benchmark' not found in root project 'matrixsum'.
        at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:117)
        at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:82)
        at org.gradle.execution.commandline.CommandLineTaskParser.parseTasks(CommandLineTaskParser.java:42)
        at org.gradle.execution.TaskNameResolvingBuildConfigurationAction.configure(TaskNameResolvingBuildConfigurationAction.java:46)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:58)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$200(DefaultBuildConfigurationActionExecuter.java:26)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter$2.proceed(DefaultBuildConfigurationActionExecuter.java:66)
        at org.gradle.execution.DefaultTasksBuildExecutionAction.configure(DefaultTasksBuildExecutionAction.java:45)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:58)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$200(DefaultBuildConfigurationActionExecuter.java:26)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter$2.proceed(DefaultBuildConfigurationActionExecuter.java:66)
        at org.gradle.execution.ExcludedTaskFilteringBuildConfigurationAction.configure(ExcludedTaskFilteringBuildConfigurationAction.java:48)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:58)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$200(DefaultBuildConfigurationActionExecuter.java:26)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter$1.run(DefaultBuildConfigurationActionExecuter.java:44)
        at org.gradle.internal.Factories$1.create(Factories.java:26)
        at org.gradle.api.internal.project.DefaultProjectStateRegistry.withLenientState(DefaultProjectStateRegistry.java:134)
        at org.gradle.api.internal.project.DefaultProjectStateRegistry.withLenientState(DefaultProjectStateRegistry.java:126)
        at org.gradle.execution.DefaultBuildConfigurationActionExecuter.select(DefaultBuildConfigurationActionExecuter.java:40)
        at org.gradle.initialization.DefaultTaskExecutionPreparer.prepareForTaskExecution(DefaultTaskExecutionPreparer.java:38)
        at org.gradle.initialization.BuildOperatingFiringTaskExecutionPreparer$CalculateTaskGraph.populateTaskGraph(BuildOperatingFiringTaskExecutionPreparer.java:82)
        at org.gradle.initialization.BuildOperatingFiringTaskExecutionPreparer$CalculateTaskGraph.run(BuildOperatingFiringTaskExecutionPreparer.java:57)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.initialization.BuildOperatingFiringTaskExecutionPreparer.prepareForTaskExecution(BuildOperatingFiringTaskExecutionPreparer.java:45)
        at org.gradle.initialization.DefaultGradleLauncher.prepareTaskExecution(DefaultGradleLauncher.java:214)
        at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:149)
        at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:130)
        at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:110)
        at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:60)
        at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:57)
        at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:85)
        at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:78)
        at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:189)
        at org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:40)
        at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:78)
        at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:57)
        at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:31)
        at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
        at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:63)
        at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
        at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:39)
        at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:51)
        at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:45)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
        at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:45)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:50)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:47)
        at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:80)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:47)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:31)
        at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:42)
        at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:28)
        at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:78)
        at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:52)
        at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:60)
        at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:38)
        at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:68)
        at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:38)
        at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:37)
        at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:26)
        at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43)
        at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29)
        at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:60)
        at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:32)
        at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
        at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
        at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:48)
        at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:32)
        at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:68)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:27)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
        at org.gradle.util.Swapper.swap(Swapper.java:38)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:82)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
        at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)


* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.1.1/userguide/command_line_interface.html#sec:command_line_warnings

BUILD FAILED in 1s

real    0m1,674s
user    0m1,648s
sys 0m0,108s

Il me paraissait clair que le titre n’était là que pour attirer le chaland

Il faut aussi garder à l’esprit que quand tu écris au présent de vérité générale quelque chose qui est généralement faux, tu écris quelque chose de faux. Des titres moins racoleurs auraient pu être «  Java et Kotlin peuvent parfois être aussi rapides que C++ », « Dans cet exemple, Java et Kotlin sont aussi rapides que C++ » ou « Quand Java et Kotlin peuvent être aussi rapides que C++ ».

Bonne journée,

+0 -0

OK merci, je teste ça après-demain soir (ce soir je ne suis pas chez moi) et mets le billet à jour. Si c’est assez intéressant, je tenterai d’en faire un article.

@r0anne essaie ./gradlew jmh plutôt.

Il faut aussi garder à l’esprit que quand tu écris au présent de vérité générale quelque chose qui est généralement faux, tu écris quelque chose de faux. Des titres moins racoleurs auraient pu être «  Java et Kotlin peuvent parfois être aussi rapides que C++ », « Dans cet exemple, Java et Kotlin sont aussi rapides que C++ » ou « Quand Java et Kotlin peuvent être aussi rapides que C++ ».

Je sais que le titre est faux, au moins partiellement. Je ne veux pas en faire un titre moins racoleur. C’est tout l’intérêt de l’expérience sur le titre : qu’il soit le plus racoleur possible sans être totalement faux.

Voici les résultats Java chez moi si ça peut t’intéresser, sur le même processeur.

> Task :jmh


Result "fr.spacefox.matrixsum.MatrixElementsSumBenchmark.indicesKt":
  11934784.517 ns/op


# Run complete. Total time: 00:33:10

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                                      (size)  Mode  Cnt          Score   Error  Units
MatrixElementsSumBenchmark.bestRatings             10  avgt    2        150.877          ns/op
MatrixElementsSumBenchmark.bestRatings            100  avgt    2       3986.013          ns/op
MatrixElementsSumBenchmark.bestRatings           1000  avgt    2     146129.002          ns/op
MatrixElementsSumBenchmark.bestRatings          10000  avgt    2    7293083.917          ns/op
MatrixElementsSumBenchmark.bestRatingsArray        10  avgt    2         57.967          ns/op
MatrixElementsSumBenchmark.bestRatingsArray       100  avgt    2       1237.602          ns/op
MatrixElementsSumBenchmark.bestRatingsArray      1000  avgt    2      53225.649          ns/op
MatrixElementsSumBenchmark.bestRatingsArray     10000  avgt    2    3429098.842          ns/op
MatrixElementsSumBenchmark.bestRatingsArrayKt      10  avgt    2         45.263          ns/op
MatrixElementsSumBenchmark.bestRatingsArrayKt     100  avgt    2       1279.822          ns/op
MatrixElementsSumBenchmark.bestRatingsArrayKt    1000  avgt    2      53033.270          ns/op
MatrixElementsSumBenchmark.bestRatingsArrayKt   10000  avgt    2    3902175.760          ns/op
MatrixElementsSumBenchmark.bestRatingsKt           10  avgt    2        132.498          ns/op
MatrixElementsSumBenchmark.bestRatingsKt          100  avgt    2       4057.588          ns/op
MatrixElementsSumBenchmark.bestRatingsKt         1000  avgt    2     138158.562          ns/op
MatrixElementsSumBenchmark.bestRatingsKt        10000  avgt    2    7037232.834          ns/op
MatrixElementsSumBenchmark.branches                10  avgt    2        206.159          ns/op
MatrixElementsSumBenchmark.branches               100  avgt    2       8486.831          ns/op
MatrixElementsSumBenchmark.branches              1000  avgt    2     596782.461          ns/op
MatrixElementsSumBenchmark.branches             10000  avgt    2   91023703.072          ns/op
MatrixElementsSumBenchmark.branchesArray           10  avgt    2        107.374          ns/op
MatrixElementsSumBenchmark.branchesArray          100  avgt    2       6059.565          ns/op
MatrixElementsSumBenchmark.branchesArray         1000  avgt    2     784543.890          ns/op
MatrixElementsSumBenchmark.branchesArray        10000  avgt    2   61021689.701          ns/op
MatrixElementsSumBenchmark.branchesArrayKt         10  avgt    2        196.791          ns/op
MatrixElementsSumBenchmark.branchesArrayKt        100  avgt    2      12227.539          ns/op
MatrixElementsSumBenchmark.branchesArrayKt       1000  avgt    2     662630.027          ns/op
MatrixElementsSumBenchmark.branchesArrayKt      10000  avgt    2   95143473.284          ns/op
MatrixElementsSumBenchmark.branchesKt              10  avgt    2        416.540          ns/op
MatrixElementsSumBenchmark.branchesKt             100  avgt    2      13457.229          ns/op
MatrixElementsSumBenchmark.branchesKt            1000  avgt    2    1061637.649          ns/op
MatrixElementsSumBenchmark.branchesKt           10000  avgt    2  116003889.630          ns/op
MatrixElementsSumBenchmark.indices                 10  avgt    2        688.237          ns/op
MatrixElementsSumBenchmark.indices                100  avgt    2      12971.844          ns/op
MatrixElementsSumBenchmark.indices               1000  avgt    2     306363.723          ns/op
MatrixElementsSumBenchmark.indices              10000  avgt    2   10299360.842          ns/op
MatrixElementsSumBenchmark.indicesArray            10  avgt    2        455.080          ns/op
MatrixElementsSumBenchmark.indicesArray           100  avgt    2       8093.962          ns/op
MatrixElementsSumBenchmark.indicesArray          1000  avgt    2     418336.129          ns/op
MatrixElementsSumBenchmark.indicesArray         10000  avgt    2   33525317.888          ns/op
MatrixElementsSumBenchmark.indicesArrayKt          10  avgt    2        651.695          ns/op
MatrixElementsSumBenchmark.indicesArrayKt         100  avgt    2       9043.850          ns/op
MatrixElementsSumBenchmark.indicesArrayKt        1000  avgt    2     393504.013          ns/op
MatrixElementsSumBenchmark.indicesArrayKt       10000  avgt    2   31932358.584          ns/op
MatrixElementsSumBenchmark.indicesKt               10  avgt    2        870.941          ns/op
MatrixElementsSumBenchmark.indicesKt              100  avgt    2      13561.697          ns/op
MatrixElementsSumBenchmark.indicesKt             1000  avgt    2     316115.824          ns/op
MatrixElementsSumBenchmark.indicesKt            10000  avgt    2   11934784.517          ns/op

Benchmark result is saved to /tmp/matrixsum/build/reports/jmh/results.txt

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.1.1/userguide/command_line_interface.html#sec:command_line_warnings

Il a aussi fallu modifier le build.gradle pour éviter une erreur, en ajoutant :

jmh {
    duplicateClassesStrategy = 'warn'
}

Je viens d’essayer et… bon, déjà il faut activer les tests qui m’intéressent, et pour retrouver des temps qui ressemblent à ceux qu’il donne dans le fichier, il faut compiler en mode release :

$ cmake -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_LTO=true 

Et surtout je ne retrouve pas les comportements décrits par l’auteur dans son premier journal. Par exemple, chez moi Indices n’est jamais plus rapide que BestRatings, sauf sur une matrice de 16384 * 16384 (mais cette valeur particulière est louche tellement elle est plus lente que la précédente sur cet algo en particulier).

Bref, tout ça est de plus en plus étrange…

$ ./matrix-elements-sum-GNU 
2020-02-19 22:17:38
Running ./matrix-elements-sum-GNU
Run on (12 X 3800 MHz CPU s)
CPU Caches:
  L1 Data 32K (x6)
  L1 Instruction 32K (x6)
  L2 Unified 512K (x6)
  L3 Unified 16384K (x2)
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
***WARNING*** Library was built as DEBUG. Timings may be affected.
----------------------------------------------------------
Benchmark                   Time           CPU Iterations
----------------------------------------------------------
branches/2                 17 ns         17 ns   37861670
branches/4                 30 ns         30 ns   24544552
branches/8                 68 ns         68 ns   10217721
branches/16               195 ns        195 ns    3536421
branches/32               626 ns        626 ns    1123552
branches/64              1930 ns       1930 ns     361386
branches/128             6677 ns       6676 ns     103114
branches/256            22741 ns      22737 ns      30492
branches/512            90619 ns      90609 ns       7663
branches/1024          351149 ns     351096 ns       1995
branches/2048         1297126 ns    1296944 ns        541
branches/4096         4938292 ns    4936963 ns        143
branches/8192        19175564 ns   19171260 ns         37
branches/16384       73505492 ns   73489286 ns          8
indices/2                  19 ns         19 ns   37108551
indices/4                  26 ns         26 ns   27092616
indices/8                  43 ns         43 ns   16096387
indices/16                 92 ns         92 ns    7662138
indices/32                205 ns        204 ns    3417331
indices/64                504 ns        504 ns    1000000
indices/128              1394 ns       1394 ns     509463
indices/256              3856 ns       3855 ns     179301
indices/512             11445 ns      11443 ns      63484
indices/1024            31926 ns      31881 ns      24254
indices/2048            83951 ns      83928 ns       8626
indices/4096           216470 ns     216420 ns       3245
indices/8192           599523 ns     599378 ns       1101
indices/16384         2918289 ns    2917328 ns        238
best_ratings/2              4 ns          4 ns  181670264
best_ratings/4              7 ns          7 ns   98354197
best_ratings/8             17 ns         17 ns   38914739
best_ratings/16            51 ns         51 ns   13717924
best_ratings/32           112 ns        112 ns    6198078
best_ratings/64           253 ns        253 ns    2624169
best_ratings/128          634 ns        634 ns    1086961
best_ratings/256         2334 ns       2333 ns     303589
best_ratings/512         9888 ns       9886 ns      70159
best_ratings/1024       26251 ns      26245 ns      26255
best_ratings/2048       73506 ns      73495 ns       9210
best_ratings/4096      205057 ns     205026 ns       3513
best_ratings/8192      621761 ns     621629 ns       1090
best_ratings/16384    6276404 ns    6274517 ns        115

Salut (j’arrive après la bataille) !

La raison pour laquelle BestRatings est beaucoup plus lent avec une grosse matrice, c’est… que la matrice est plus grosse que ton cache processeur. Et que vu la représentation utilisée, cet algo est hyper inefficace puisqu’il charge n fois chaque ligne (en gros) dans le cache au lieu d’une pour les autres (enfin si j’ai bien lu).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte