Journal Quand Pythran fait tourner du Python plus vite que du C++, c'est que...

Posté par (page perso) . Licence CC by-sa
31
26
juin
2014

Yo standard (un stdyo en quelque sorte)

Un utilisateur de Pythran m'a récemment conseiller de regarder le benchmark que voilà et de comparer Pythran à l'ensemble.

L'article associé essaie de comparer l'implémentation d'un algo dans différents langages, un peu à la computer language benchamrk. Pourquoi pas.

Ni une, ni deux, je choppe les sources C++, Python et Python + Jit (Mumba en l'occurrence), je compile et lance les benchs. Rien de très surprenant:

$ python RBC_Python.py
[...]
105.5s

puis en C++

$ clang++ -O3 RBC_CPP.cpp -o RBC_CPP
$ ./RBC_CPP
[...]
2.14s

(2.12 avec g++)

et enfin avec le JIT de Numba (après chauffe)

$ python RBC_Python_Numba.py
[...]
2.7s

Pas mal, presque aussi rapide que le C++ (TM) juste un décorateur a ajouter, ça parait magique !

Naïvement, je porte la fonction portant le marqueur @autojit dans un module Python séparé que je compile avec Pythran

$ pythran RBC.py
$ python RBC_Python_Pythran.py
[...]
1.8s

Et voilà Pythran génère du code plus rapide que du C++.
Mmmmh
Mmmmh

Ça veut rien dire ce truc, Pythran génère du C++, alors WTF ?

J'avoue ne même pas avoir regardé les implémentations Python / Python_Numba / C++. Mais franchement ça fait réfléchir sur la mesure de performance en général.

Mes conclusions :

  • Ce genre de benchmark mesure surtout la connaissance de celui qui l'a écrit dans les différents langages concernés

  • Quitte a pas être bon en dev, autant faire du Python :-)

  • Laissez le calcul haute performance aux spécialistes et pas au économistes :-)

et sinon… j'aime les moulinettes à fromage, et vous ?

  • # et avec pypy ?

    Posté par (page perso) . Évalué à 5.

    Les résultats sont plutôt impressionnants avec Pythran, félicitations.

    Mais la première chose qui me vient à l'esprit, c'est « quid de pypy ? »

    Accessoirement, qu'en est-il de la RAM consommée ?

    • [^] # Commentaire supprimé

      Posté par . Évalué à 10.

      Ce commentaire a été supprimé par l'équipe de modération.

      • [^] # Re: et avec pypy ?

        Posté par . Évalué à 1. Dernière modification le 27/06/14 à 09:53.

        Commentaire supprimé par l'auteur

      • [^] # Re: et avec pypy ?

        Posté par . Évalué à 1.

        peux-tu expliquer en quelques mots (ou lignes)? j'ai un peu la flemme/pas trop le temps de lire le pdf de plus de 100 pages que tu as mis en lien…

        • [^] # Re: et avec pypy ?

          Posté par (page perso) . Évalué à 10.

          Le code a très probablement été adapté du Matlab ou du FORTRAN qui sont en column major alors que C/C++ sont en row major. Dans le cas présent, à chaque fois que la boucle interne passe à l'itération suivante, au lieu d'accéder à l'élément mémoire d'à côté, on fait un saut, qui peut sortir du cache, et là c'est le drame. Un défaut de cache à chaque itération de boucle, ça pardonne pas.

          • [^] # Re: et avec pypy ?

            Posté par (page perso) . Évalué à 1.

            Ce n'est pas un x100, mais en stockant les tableaux simplement dans un ordre plus logique pour le C, je passe de ma machine (en moyenne) de 1.53s à 1.42. La localité des données reste toute pourrie quand même (un tableau de structure packant les 5 doubles ensemble seraient surement plus efficace au final que ces 5 tableaux de double).

            • [^] # Commentaire supprimé

              Posté par . Évalué à 2. Dernière modification le 27/06/14 à 17:13.

              Ce commentaire a été supprimé par l'équipe de modération.

        • [^] # Re: et avec pypy ?

          Posté par . Évalué à 8.

          Le cache en CPU, fonctionne par ligne appelée ligne de cache. Celle-ci contient 4 ou plus mot de 32bits. Lorsque l'on lit une variable, le CPU demande à la RAM la valeur et en profite pour mettre dans la ligne de cache la RAM autour de la valeur demandée.
          En C et C++, sur un tableau à deux dimensions, si on a un pointeur sur la ligne 3 et la colonne 4, un pointeur++ pour voir le mot suivant en RAM donnera la valeur de la ligne 3 colonne 5 ou dans le cas de 4 colonnes, la ligne 4 colonne 1.

          En complément, pour optimiser de manière simple, on peut mettre ensemble (côte à côte) des valeurs d'une structure souvent accédée ensemble, comme ça, on a de bonne chance qu'elle soit sur la même ligne de cache et que une fois le premier champ accédé, les autres soient déjà dans le cache.

    • [^] # Re: et avec pypy ?

      Posté par (page perso) . Évalué à 4.

      Mais la première chose qui me vient à l'esprit, c'est « quid de pypy ? »

      avec PyPy 2.0.2 with GCC 4.7.2

      5.6s

      mais le support de numpy est encore balbutiant en pypy…

  • # Comparaison Fortran / C++

    Posté par (page perso) . Évalué à 2.

    L'article associé est quand même intéressant à lire. Je viens de compiler, sur ma vielle machine, les deux programmes en c++ et en Fortran. Avec la même optimisation (O3), gFortran exécute le programme en un peu moins de 4s alors que celui en c++ met un poil de plus de 4s. Dans l'article (mais c'est sur Mac que c'est testé) le programme en c++ est donné comme étant 5% plus rapide que Fortran.

    • [^] # Re: Comparaison Fortran / C++

      Posté par . Évalué à 5.

      5% ce n'est vraiment pas grand chose, et montre potentiellement plus les limitations d'un compilateur que celles d'un langage. J'ai des confrères qui peuvent montrer que gfortran va parfois faire des appels à malloc alors que (par exemple) ifort va correctement allouer des trucs sur la pile.

  • # Méthode de mesure.

    Posté par . Évalué à 10.

    Hum,

    Je suis d'accord avec toi, quelque chose ne va pas.

    J'ai regardé rapidement le code C++ (enfin, l'un des deux fichiers). Il n'y a qu'une seule mesure de faite, ce qui ne veut du coup strictement rien dire. Il faudrait exécuter le même code plusieurs fois, dans plusieurs types de conditions différentes pour pouvoir réellement mesurer. En gros, là, le code fait quelque chose de ce genre :

    // Fonctions à utiliser …
    
    int 
    main(void)
    {
        double start = get_clock();
        /* trucs compliqués qui prennent un peu de temps */
        double stop = get_clock();
    
        cout << "Time: " << stop-start << endl;
    
        return 0;
    }

    Alors qu'en pratique, il faudrait garantir 2-3 trucs :

    1. Il faut garantir que le code s'exécutera toujours sur le même cœur (utilisation de sched_setaffinity etc.), pour éviter que l'ordonnanceur ne migre le programme d'un cœur à un autre (peu probable, mais ça arrive…)
    2. Il faut effectuer plusieurs mesures, en utilisant des facteurs de répétition à différents endroits.
    3. Comme tu le disais, la façon dont un code est exprimé va plus ou moins aider le compilateur. Dans beaucoup de cas, faire du code simple aide énormément le compilateur.

    Donc au lieu du code précédent, il faudrait un truc plutôt dans ce genre :

    /* Fonctions à déclarer/définir ... */
    
    void compute(void *arg)
    {
        utiliser_sched_setaffinity_ici();
    
    
        return 0;
    }
    
    int 
    main(void)
    {
        std::queue<double> timings;
        for (int i = 0; i < META_REPETS; ++i) { 
            flush_caches();
            double start = get_clock();
            for (int i = 0; i < NB_REPETS; ++i) {
                /* calculs faits ici */
            }
            double stop = get_clock();
            timings.push_back(stop-start);
        }
        do_clever_stats_with(timings); // moyenne, écart-type, tout ça
    
        return 0;
    }

    Dans ce code, on pourra ensuite faire varier META_REPETS et NB_REPETS pour vérifier les comportements/temps d'exécution si les caches de données sont chauffés ou pas, etc.

    Sinon, j'ai regardé un peu plus le code, et certaines boucles sont écrites « en Fortran », c'est-à-dire qu'elles sont écrites comme si les tableaux n-dimensionnels étaient stockés en « column major ». Par exemple [ici(https://github.com/jesusfv/Comparison-Programming-Languages-Economics/blob/master/RBC_CPP.cpp), lignes 93-97. Bon dans ce cas ce n'est pas très grave (c'est une boucle exécutée une fois seulement), mais ça la fout mal quand même.

    Sinon le reste du code me semble largement optimisable, et il faudrait voir comment triturer un peu tout ça. J'ai l'impression que certaines boucles sont séparées parce qu'il était plus facile de raisonner ainsi plutôt que parce que c'est nécessaire, que d'autres devraient au contraire être fissionnées car ne favorisant pas trop la localité, etc.

    • [^] # Re: Méthode de mesure.

      Posté par (page perso) . Évalué à 4.

      Il faut garantir que le code s'exécutera toujours sur le même cœur (utilisation de sched_setaffinity etc.), pour éviter que l'ordonnanceur ne migre le programme d'un cœur à un autre (peu probable, mais ça arrive…)

      J'ai du mal à comprendre ce genre d'optimisation. Si on fait tourner le test plusieurs fois, c'est justement pour que ce genre d'erreur n'ait pas d'influence. Et si l’ordonnanceur change le cœur à chaque fois, c'est qu'il y a un problème, soit dans l'implémentation, soit dans la machine de test.

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

      • [^] # Re: Méthode de mesure.

        Posté par . Évalué à 4.

        Fixer un processus/thread sur un cœur donné n'est pas fait dans le but d'optimiser, mais de limiter le bruit lors de la « mesure ». À cela, il faut ajouter que malgré toutes les précautions qu'on peut prendre, il vaut mieux lire « le temps » depuis la même horloge pour commencer et terminer la mesure (entre les cœurs ou même les processeurs, les horloges peuvent varier suffisamment pour récupérer un temps négatif si le programme mesuré a un temps d'exécution assez court).

        Dans le contexte de la programmation parallèle par contre, s'assurer que les threads sont bien fixés sur un cœur, cela permet de garantir certaines propriétés concernant la localité pour l'accès aux données (s'assurer que les threads accèdent toujours aux mêmes caches de données, etc.).

        • [^] # Re: Méthode de mesure.

          Posté par . Évalué à 6.

          À cela, il faut ajouter que malgré toutes les précautions qu'on peut prendre, il vaut mieux lire « le temps » depuis la même horloge pour commencer et terminer la mesure

          Au passage c'est chaque langage qui mesure lui même son temps d'exécution, il vaudrait mieux je pense que ce soit fait ailleurs et du coup exactement de la même façon pour chacun.

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: Méthode de mesure.

            Posté par . Évalué à 2.

            "time" permet de récupérer le temps CPU user, donc à priori assez indépendant vis-à-vis de l'exécution d'autres programmes sur la machine, non ?

            • [^] # Re: Méthode de mesure.

              Posté par (page perso) . Évalué à 4.

              Ça va prendre en compte tout le lancement de l'application, pour comparer le temps de calculs en C et en Java, ça risque d'être problématique. Sauf si ton temps de calcul est très largement supérieur au temps de lancement de la VM (mais ce n'est pas le cas ici, et pour le savoir, il faut le mesurer).

              « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

  • # Du grand n'importe quoi

    Posté par . Évalué à 10. Dernière modification le 27/06/14 à 03:57.

    Mes conclusions :

    • Ce genre de benchmark mesure surtout la connaissance de celui qui l'a écrit dans les différents langages concernés

    • […]

    • Laissez le calcul haute performance aux spécialistes et pas au économistes :-)

    J'ai envie de dire que ce genre de benchmark mesure surtout ma méconnaissance des langages.

    Déjà un problème conceptuel. Dans l'article ils décrivent leur problème, mais ils ne décrivent pas leur algo. Qui semble aller de soi quand on lit l'article, mais quand on regarde dans le détail il y a plein de petits trucs qui peuvent tout changer. Il aurait été un minimum de mettre dans l'article ou dans une annexe l'algo de manière dissociée du language.

    Un autre problème conceptuel est sur le choix de l'algo utilisé dans la comparaison. C'est un algo qui est vectorisable, où beaucoup d'étape (peut-être toutes) peuvent êtres écrites sous forme de calcul matriciel. Ça implique pas mal de chose, quand à l'utilisation des libs adaptées et quand à la capacité des differents compilateurs de de faire des opérations utiles comme changer l'ordre des boucles.

    Le point du calcul matriciel m'amène à voir que dans certains programmes (MATLAB par exemple) certaines étapes vectorisables sont vectorisés, mais pas d'autres (pourquoi ?) et dans d'autres (en C++ par exemple) ces mêmes étapes ne sont pas vectorisées (pourquoi ? ils ne connaissent pas les libs comme armadillo ?). Dans un benchmark, le minimum est de mettre toutes les possiblilités dans des conditions équivalentes. Soit on utilise les subtilitées et les capacités de chaque language pour optimiser au mieux le code, et dans ce cas on compare les langages dans un cas très particulier, les conclusions sont attachées à ce cas particulier. Soit on utilise rigoureusement le même algo (et ne vectorise pas dans un code sans le faire dans l'autre), et dans ce cas on a une comparaison tellement générale qu'elle ne retranscrira pas la particularité d'un langage à résoudre un problème donné. Mais en faisant un mélange entre les deux, au lieu d'obtenir une conclusion partielle, ils obtiennent une conclusion invalide.

    Après dans le détail, je viens juste de l'évoquer, pour les quelques langages que je connais, je saute au plafond. En C++ outre les boucles dans un ordre étrange, c'est du code vectorisable, il y a des libs pour ça. Dans un des code, j'ai eu l'horreur de voir une definition d'un template Matrix à la main. Moi aussi quand j'étais jeune j'ai codé intégralement sans rien y connaître mes libs de calcul matriciel (j'ai même écrit une lib d'algèbre linéaire en PHP, oui j'ai honte), oh ben on a des opérations inefficace aussi bien en temps qu'en mémoire, c'est étrange.

    Au niveau du choix des langages, on a également de qui être dubitatif, Mathematica, vraiment. Oui un logiciel de calcul symbolique n'est pas un logiciel de calcul numérique. MATLAB, c'est efficace pour du calcul matriciel, oui les trois premières lettres du noms ne sont pas là par hasard. On peut être dubitatif également sur le soin apporté à chaque logiciel, par exemple en MATLAB on fait un MEX, en R on pourrait implémenter une procédure C ou C++ (qui aurait les perfs du C, et avec les libs adaptés genre Rcpp ça a le niveau de simplicité du R), et après la conclusion sur R est négative, on s'y attendait.

    Après pour les conclusions, elles ne sont pas vraiment fausse (enfin certaines me laissent séptique pour ne pas dire ahuri), mais elles enfoncent des portes ouvertes. Oui les langages compilés sont plus rapide pour faire des boucles, oui les langages adaptés pour le calcul matriciel (R, MATLAB) ne sont pas adaptés pour faire des boucles, oui les langages de calculs symbolique ne sont pas adaptés pour faire du calcul numérique, oui la compilation JIT c'est le bien.

    Bref, ça vaut rien ce benchmark. L'article ne semble pas publié dans une revue, autrement il aurait clairement mérité une réponse en règle.

  • # Et sinon ?

    Posté par . Évalué à 5.

    Et sinon, le projet se porte toujours bien ? Une communauté s'est formée ?
    Tu as des retours sur du code qui est mis en production et/ou qui est critique ?

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Et sinon ?

      Posté par (page perso) . Évalué à 5.

      On avance, on avance on avance, c'est une évidence… (air connu)

      ce qui est toujours mieux que

      Rame, rame rameurs, ramez

      Plus sérieusement, on a quelques utilisateurs réguliers de part le monde (au moins un en France, un en Grande Bretagne et un aux US) qui remontent des bugs de temps en temps. D'après pypi on aurait plus que ça, mais c'est dur de savoir vraiment. Et quelques vues sur github.

      On a encore pleins d'idées à implémenter, on essaie de faire de la bonne ingénierie, et on manque de temps… mais on s'amuse bien (on = 3 devs réguliers).

  • # Pas de conclusion hâtive

    Posté par (page perso) . Évalué à 3.

    Mesurer la performance d'un programme est quelque chose de très difficile; d'abord parcequ'il faut définir la performance, ensuite parceque toutes les approches naïves fonctionnent assez mal.

    Si on se base sur le temps d'éxécution, la première chose qu'on demande à une mesure est d'être reproductible. Les éxécutions multiple du même programme peuvent montrer des temps très différents, en principe on mesure donc plutôt une performance empirique en faisant une moyenne dans des conditions normalisées — sans cela on ne peut pas grarantir la reproductibilité.

    Les biais typiques sont:
    - la machine
    - l'OS
    - le bruit de fond de l'OS (i.e. processus concurrents)
    - les variables d'environnement (même non utilisées!)

    Même en normalisant toutes ces conditions, on peut observer des facteurs 10 sur la vitesse d'éxécution d'un programme. (Tu éxécutes ton programme Lundi puis Mardi, directement après un fresh-reboot en single-user et les temps d'éxécution diffèrent d'un facteur 10)!

    Si tu veux mesurer sérieusement les peformances d'un programme, tu peux par exemple décider du protocole suivant:

    • choix de la machine et l'OS;
    • écriture d'un script qui éxécute ton programme N+1 fois et se souvient des N dernières mesures de temps, en ajoutant une variable d'environnement inutile de longueur entre 1 et 8 (pour avoir tous les types d'alignement mémoire possibles);
    • boot en single-user et éxécution du script.

    Un référence classique sur le sujet:

    “Producing Wrong Data Without Doing Anything Obviously Wrong!”

    http://www-plan.cs.colorado.edu/diwan/asplos09.pdf

    • [^] # Re: Pas de conclusion hâtive

      Posté par . Évalué à 5.

      Pour mitiger les points que tu cites des problèmes de mesure de la performance, j'applique plusieurs recettes :
      - mesurer des "ticks" précis (rdtsc), cela donne un nombre de cycle d'horloge, qui est un peu moins sensible au changement de fréquence entre type de cpu, et qui donne une idée de la granularité du test (passer de 10 à 11, donne 10% de variation, mais on est dans le bruit de fond).
      - ne jamais faire de moyenne, cela sert surtout à masquer toutes problèmes
      - écrire des courbes à la place : cela permet de détecter les patterns spécials. Souvent un même test avec avoir 3 ou 4 paliers (l'un 90% du temps, le deuxième 9%, et le reste moins de 1%). On peut y voir un changement de contexte, un page miss, etc…

      Le truc à faire est vraiment de faire une courbe. Cela permet aussi de voir le "chauffage des caches", genre sur 10 itérations, la 1ere est lente, puis cela s’accélère pour stabiliser à la 3ième itérations. Nettoyer les caches ne fait que pessimiser cela, cela aide peu.

      "La première sécurité est la liberté"

  • # Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

    Posté par . Évalué à 1.

    C'est simple, je n'ai lu qu'en très rapide ce fichier.

    Déjà la remarque des tableaux qui à été faite à plusieurs reprises, mais aussi des choses comme:

    • iteration = iteration+1; et ++iteration; c'est pour les chiens?
    • for (nCapital = 0;nCapital<nGridCapital;++nCapital)avec nCapital un entier qui sert d'index. Un dev C aurait utilisé une arithmétique de pointeurs, un dev C++ des itérateurs.

    Pour les tableaux qui marchent à l'envers (par rapport au langage), on pourrait excuser, ce sont des choses qui peuvent être spécifiques à des langages, mais ça, ce sont des idiosyncrasies de base du langage C++ quand même.

    Il faut en avoir de grosses pour oser ensuite dire qu'un langage est plus rapide qu'un autre parce qu'on à bâclé le programme du second, quand même. C'est le seul point pour lequel ce bench mérite "le respect".

    • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

      Posté par . Évalué à 7.

      Un dev C aurait utilisé une arithmétique de pointeurs, un dev C++ des itérateurs.

      Oui pour les itérateurs, mais pour les pointeurs, il faut faire attention. Si tu boucles avec des pointeurs, le compilateur ne présuppose plus rien. Alors qu'avec les indices, il est capable d'optimiser aussi bien que les pointeurs, voir mieux car comme il sait où il va, il peut faire du prefetch.

      Comme disais Carmack : Ne supposez pas que ça va plus vite, vérifiez-le !

    • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

      Posté par (page perso) . Évalué à 3.

      iteration = iteration+1; et ++iteration; c'est pour les chiens?

      Parce que ça optimise quelque chose en termes de performances de passer à la version pre-increment ?

      • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

        Posté par . Évalué à 7.

        Je crois que les compilateurs optimisent très bien ce genre de choses.

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

          Posté par (page perso) . Évalué à 2.

          Effectivement mais il n'y a aucune différence entre le code généré pour les deux méthodes.
          En compilant avec un -O3 le code suivant

          int main(int argc, char** argv)
          {
              int iteration = 0;
              while (iteration <= 100)
              {
                  iteration = iteration + 1;
              }
              return 0;
          }

          on obtient le même code assembleur que pour la version pre-increment.

          • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

            Posté par . Évalué à 2.

            Oui c'est ce que je dis :

            v += var + 1;
            v++; // sans autre affectation
            ++v; // sans autre affectation

            sont identiques.

            Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

            • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

              Posté par (page perso) . Évalué à 2.

              Alors on est bien d'accord,

              iteration = iteration+1; et ++iteration; c'est pour les chiens?

              est un argument sans valeur contre le benchmark.

              • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

                Posté par . Évalué à -6.

                Mon point, c'est surtout qu'on ne fait pas de bench dans un langage qu'on ne maîtrise pas, et je n'ai souvenir d'aucun code source dans un langage dérivé de C qui utilise encore le "foo = foo + 1;". Excepté les 1ers codes source que j'aie vus à l'école, mais j'ose espérer que les étudiants ont autre chose à faire que des benchmarks?

                • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

                  Posté par . Évalué à 2.

                  En s'en fiche de foo = foo + 1; ou foo += 1 ou ++foo ou foo++, en C, mais aussi en C++, dès lors qu'il s'agit de types primitifs. Lorsque j'utilise le genre de boucles que j'ai décrites ailleurs dans ce journal pour faire de la mesure de performance, j'ai tendance à utiliser foo += STEP, parce que je peux vouloir faire varier des paramètres supplémentaires que je n'ai pas décrits ici. Dans tous les cas, pour un type primitif, le compilateur saura générer la séquence la plus optimale, car il sait comprendre « l'absence » de contexte dans ++foo, qui, de toute manière peut être converti en foo = foo + 1 sans perte de sens…

                  • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

                    Posté par . Évalué à -4.

                    Ça aurait été du foo+=step; figures-toi que je n'aurais pas relevé. La, c'est foo=foo+step; et même si je suis d'accord que dans ce cas, dans ce code, avec ces types, que ce soit en C ou en C++ ça ne change rien au binaire.
                    Juste, ça change la facilité de lecture du code, et ça montre que l'auteur ne semble pas habitué aux langages héritiers du C (pour faire court, parce qu'ils sont légions et intègrent tous ces raccourcis).

      • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

        Posté par . Évalué à 2. Dernière modification le 27/06/14 à 14:46.

        Parce que ça optimise quelque chose en termes de performances de passer à la version pre-increment ?

        Oui, en c++.

        Quand tu déclares en pré-incrément, tu n'a pas de copie.

        // .h
        Classe & operator++();
        
        //.cpp
        Classe & operator++()
        {
          // Code d'incrément (plus ou moins long)
          return *this;
        }

        Ici, on retourne une référence sur l'objet incrémenté.

        // .h
        Classe operator++(int); // le paramètre signifie post incrément
        
        // .cpp
        Classe operator++(int)
        {
          Classe tmp = *this; // Copie de l'objet
          // Code d'incrément
        
          // On retourne une copie de l'objet avant l'incrément. Ce ne peut-être une réf, car il est sur la pile.
          return tmp; // Copie de l'objet
        }

        Ici, on a deux copies, ce qui peut poser un problème si l'objet est gros. C'est pour ça, notamment sur les fonctions template où tu ne présuppose pas ce que tu as comme objet qu'il vaut mieux faire des pré-incrément en c++.
        Je ne sais pas si c++11 permet avec le && d'écrire les post-incrément mieux ?

        • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

          Posté par (page perso) . Évalué à 3. Dernière modification le 27/06/14 à 15:08.

          On est d'accord dans le cas d'une classe où tu définis l'opérateur mais sur un type "atomique" comme int, c'est quoi l'impact ?
          Dans le bench, c'est sur un bête int qu'on fait ça, pas de template ou quoique ce soit d'aussi compliqué dans notre cas :)

          • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

            Posté par . Évalué à 4.

            Sur un int il n'y aura aucun 1 impact. Les compilateurs moderne se débrouille très bien pour savoir quand le faire au mieux. Il y a peu être un impact avec une copie de registre si vraiment les deux valeurs sont utiles. Mais généralement, c'est du a++ dans un for, while sans besoin de la valeur initiale. On peut avoir b=a++ mais là, le compilo va d'abord copier puis incrémenter. Pareil pour les expression du type c=a + b++ où on va faire le add regA, regB (le résultat est stocké dans regA) puis inc regB. Le seul cas que je vois c'est c=a++ + b je ne sais pas si le compilateur fait jouer la commutativité pour gagner un cycle… je ne suis pas sûr de la pertinence de se genre d'optimisation.


            1. enfin, vraiment mineur avec les compilateurs modernes. Si tu en es à gratter au cycle près, c'est qu'il y a un problème de conception générale du programme àmha. 

    • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

      Posté par (page perso) . Évalué à 4. Dernière modification le 27/06/14 à 14:04.

      Un dev C aurait utilisé une arithmétique de pointeurs, un dev C++ des itérateurs.

      ça aurait changé quoi à l'arrivée ? rien, le code n'irait ni plus vite ni moins vite.

      • [^] # Re: Un code d'un langage que l'on ne connaît pas ne peut pas servir pour un bench!

        Posté par . Évalué à -7.

        On peut effectivement faire confiance aux compilateurs modernes pour les optimisations. Je dis juste que, en ayant lu rapidement (en diagonale) le source je constate déjà des éléments qui montrent l'absence de compréhension du langage par son auteur.

        Sinon, dans l'absolu, l'arithmétique de pointeurs, ce n'est pas pour rendre le code illisible, c'est parce qu'il en résulte une économie dans le nombre d'instructions machine.

        Dans le cas d'un index (il arrive que ce soit utile d'utiliser des indexes, je ne dis pas le contraire. Juste, pas ici.):

        • Initialisation de l'index (i=0)
        • calcul du pointeur final (foo[i])
        • déréférencement du pointeur final (bar=foo[i])
        • incrémentation de l'index (i++)

        Dans le cas d'un pointeur:

        • initialisation du pointeur (pi=&foo)
        • déréférencement du pointeur (bar=*pi)
        • incrémentation du pointeur (pi++)

        Une instruction de moins, sur un pointeur, à chaque itération (sachant qu'un extrait du code donne ça: diff = std::abs(mValueFunction[nCapital][nProductivity]-mValueFunctionNew[nCapital][nProductivity]); ==> plus d'un index, et mieux, déréférencements inutiles car doublés…).

        Mais bon, on peut toujours écrire du code pourri pour faire un bench, se reposer sur les optimisations du compilo (sachant qu'une optimisation de vitesse peut parfois apporter moins de perfs de vitesse qu'une optim sur l'espace mémoire, et que donc il faut savoir quelle optimisation utiliser en fonction du code…) et ensuite accuser le langage.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.