Partage
  • Partager sur Facebook
  • Partager sur Twitter

Faut-il éviter les appels de fonction ?

    14 avril 2022 à 9:02:10

    Bonjour à tous,

    Pour certaines classes simples, j'hésitais à laisser les données publiques pour une manipulation plus aisée. Et j'en suis venu à prendre en compte la question des performances, puisque c'est dans un contexte où j'en ai besoin (calcul de collisions entre des formes géométriques).  Si on souhaite utiliser  des getter et/ou setter au lieu d'accéder directement aux données, on perd environ 20% de performance, et ce quel que soit le niveau d'optimisation à la compilation (-O1 -O2 -O3). Je m'en doutais bien, un appel de fonction n'est pas gratuit, mais je ne m'attendais pas à un tel écart.

    De manière générale, j'en déduis que découper le code en beaucoup de petites fonctions, même si cela améliore la lisibilité et le travail de conception, réduit globalement les performances, car cela engendre nécessairement des appels dans tous les sens. J'ai essayer d'inliner les fonctions, mais cela n'y change visiblement rien. Qu'en pensez-vous ?

    Un petit code pour illustrer :

    #include <iostream>
    #include <vector>
    #include <chrono>
    
    class A
    {
    public:
        A(long _a) : a{_a}{}
        long get(){return a;}
    private:
        long a;
    };
    
    struct B
    {
        B(long _a) : a{_a}{}
        long a;
    };
    
    
    int main()
    {   
        std::vector<A> v1;
        std::vector<B> v2;
        for (long i = 0; i < 100000000; ++i)
        {
            v1.push_back(i);
            v2.push_back(i);
        }
    
        long t{0};
    
        auto start1 = std::chrono::high_resolution_clock::now();
        for(auto i : v1)
        {
            t+= i.get();
        }
        auto end1 = std::chrono::high_resolution_clock::now();
    
        std::cout << t << std::endl;
        t = 0;
    
        auto start2 = std::chrono::high_resolution_clock::now();
        for(auto i : v2)
        {
            t+= i.a;
        }
        auto end2 = std::chrono::high_resolution_clock::now();
        std::cout << t << std::endl;
    
        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count() << std::endl;
        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count() << std::endl;
    }



    -
    Edité par Umbre37 14 avril 2022 à 9:11:06

    • Partager sur Facebook
    • Partager sur Twitter
      14 avril 2022 à 9:08:09

      Salut,

      inliner les fonctions ne devrait pas poser de soucis.

      D'ailleurs, même sans inliner, pour un getter ou setter simple comme ça, le compilo devrait le faire de lui même.

      Moi je pense que tu as surtout un effet de bord quelque part. Quelles sont les mesures de tes temps ? car si c'est de l'ordre de moins de 1 seconde, alors tu as aussi l'OS qui tourne derrière et peut faire varier un peu les temps.

      Tu devrais faire des tests qui te durent plusieurs secondes de calcul (voir plus de 1 minutes). 

      • Partager sur Facebook
      • Partager sur Twitter

      Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

        14 avril 2022 à 9:23:30

        Merci de ta réponse, j'ai exécuté le code précédent une cinquantaine de fois c'est toujours pareil. Environ 80 ms pour la version avec getter, environ 70 ms pour la version sans... Inliner ne pose pas de problème mais ça ne change rien.
        • Partager sur Facebook
        • Partager sur Twitter
          14 avril 2022 à 9:29:43

          Sur wandbox, avec N=30000000 (sinon ca passe pas) et O2 :

          449999985000000
          449999985000000
          10
          11

          (La différence n'est pas significative)

          Sur quickbenchmark

          Et sur goldbolt, le code generé est le meme :

          • Partager sur Facebook
          • Partager sur Twitter
            14 avril 2022 à 9:37:47

            Normalement de simple getters ont toutes les chances d’être inlinés et donc de ne pas avoir d’appel de fonction. Si vous vous intéressez à l’optimisation à ce niveau là, je vous recommande ce document qui est la référence en la matière et qui explique aussi pourquoi malgré des machines de plus en plus puissantes, on se mange encore des lenteurs et ralentissements au quotidien 

            https://www.agner.org/optimize/optimizing_cpp.pdf

            Et sinon de nos jours, oui, il y a malheureusement des concessions à faire, soit au niveau de la lisibilité et maintenabilité, soit au niveau des performances, on n’a généralement que le premier quand on utilise trop d’abstractions haut niveau

            -
            Edité par JadeSalina 14 avril 2022 à 9:41:04

            • Partager sur Facebook
            • Partager sur Twitter
              14 avril 2022 à 10:18:45

              @gbdiver Merci pour ces tests. Étonnant ! cela doit dépendre de mon compilateur ou de mon OS alors, j'obtiens toujours un écart entre 10% et 20% chez moi, comme quoi, il est bon de comparer avec d'autres environnements. Je ne suis pas un pro et ne connais pas tous les outils.

              @JadeSalina Merci beaucoup de ce complément intéressant. Vous confirmez mon intuition. Je vais lire votre article avec attention.

              Cela n'a pas de lien directe, mais je trouve que la complexité du c++ est parfois décourageante. Le header <chrono>, par exemple (et quelques autres) est un cauchemar pour moi. Je pense que cette raison est une des principales pour lesquelles les gens enseignent, étudient et pratiquent du "vieux c++". Je dis cela parce que ce sont des sujets qui reviennent souvent sur le forum. Certes, les vielles habitudes ont la vie dure, mais l'évolution du langage a une part de responsabilité de mon point de vue. Il y a vraiment de quoi faire fuire les débutants ou simples amateurs, comme moi. J'ai par exemple essayé d'étudier le code source de entt (que j'utilise parfois)... j'ai renoncé. J'en viens à me demander comment un humain normal peut écrire ou lire cela. Mais peut-être suis-je moins malin que la moyenne, ou bien il faut considérer que c'est un langage nécessitant un investissement à plein temps...

              Merci encore de votre aide. Bonne journée à tous !

              -
              Edité par Umbre37 15 avril 2022 à 0:55:53

              • Partager sur Facebook
              • Partager sur Twitter
                14 avril 2022 à 10:38:39

                > cela doit dépendre de mon compilateur ou de mon OS alors, j'obtiens toujours un écart entre 10% et 20% chez moi

                Tu peux générer et regarder l'assembleur généré. Avec -S ou /S je crois. Et il faut regarder aussi tes options de compilation. Mais sur ton code présenté, aucune raison de voir une différence.

                > Le header <chrono>, par exemple (et quelques autres) est un cauchemar pour moi

                Il y a pleins de choses qui sont plus facile a faire avec <chrono> qu'avec les vieux <ctime>. Prend différents codes utilisant <chrono> et essaie de les implémenter en C, tu verras.

                > Je pense que cette raison est une des principales pour lesquelles les gens enseignent, étudient et pratiquent du "vieux c++".

                Alors, pour les enseignants, la principale raison, c'est le manque de temps et de motivation pour se mettre a jour sur les langages.

                Et pour les étudiants, c'est justement d'avoir des enseignants (et des livres) qui ne sont pas à jour et présentent les évolutions du C++ comme des surcouches (donc de la complexité en plus).

                Parce que malheureusement, ce qu'on voit souvent dans les mauvais cours, c'est du code old school simple mais faux. Du code old school correct est souvent plus lourd et plus complexe.

                Donc oui, l'évolution du langage a eu une impact sur la complexité apparente du C++. Mais malheureusement parce que l'enseignement n'a pas suivi.

                > J'ai par exemple essayé d'étudier le code source de entt (que j'utilise parfois)... j'ai renoncé

                Oui, c'est normal, c'est un code complexe. Tu prendrais le code de linux, tu ne comprendrais rien non plus. C'est pas un problème de langage, c'est un problème général d'entrer dans un projet complexe. Meme un dev expérimenté ne rentre pas dans gros projet qu'il ne connaît pas en 10 secondes.

                > ou bien il faut considérer que c'est un langage nécessitant un investissement à plein temps...

                Si tu veux bosser sur des projets aussi gros que entt, c'est de toute façon pas des projets que tu peux facilement faire seul. On parle de projets de plusieurs centaines de milliers de lignes de code. Ca prend des dizaines d'années a un dev seul de faire un tel projet. Et des mois/années pour une équipe de devs. 

                Rien que implementer correctement std::vector te prendrais probablement des jours ou des semaines.

                Et c'est bien pour ca qu'on conseille d'utiliser dans un premier temps les outils existants. La complexité et le temps de travail pour faire les choses sans utiliser les outils existants, c'est énorme comparé a l'utilisation de ces outils. Et c'est pour ca aussi qu'on conseille de viser la qualité dans un premier temps, plutot que chercher les performances a tout prix et se perdre dans la complexité.

                -
                Edité par gbdivers 14 avril 2022 à 10:41:51

                • Partager sur Facebook
                • Partager sur Twitter
                  14 avril 2022 à 10:40:41

                  Mesurer correctement les performances d'une application est un sujet complexe. Un simple changement de répertoire d'exécution ou une variable d'environnement en plus, en moins, différente va aussi perturber les mesures. Tout comme la présence ou non d'autre code qui va altérer la chose mesurée (en agissant sur les alignements, etc). Cf [Mytkowicz], [Adelstein-Lelbach, CppCon 2015 : "Benchmarking C++ Code"], [Berger]

                  Bref ce n'est pas simple. Ceci dit, pour du code comme celui-ci, on vérifie facilement sur godbolt qu'il n'y a aucune différence coté asm et qu'en fin de compte ce qui est mesuré, c'est du bruit. Pour du microbenchmark de petites fonctions/approches alternatives comme ici, rien ne vaut les outils adaptés comme  nanobench (il serait mieux que google.benchmark d'après je ne sais plus quels spécialistes)

                  Le document d'Agner Fog est une véritable mine d'informations -- même si j'avais noté 2-3 petits écarts avec les compilos contemporains. Toujours-est il que ces fonctions là vont se faire gommer par inlining, ou par LTO, en général. Que si elles restent il y a des micro optim intéressantes à considérer (même ordre de paramètres, tail-call...). Et ne pas perdre de vue que même, et surtout d'un point de vu OO, un getter, c'est un signe de design non OO. Rajouter à ça que l'organisation des données façon OO est un frein au traitement massif de données mathématiques, d'où certains désign orientés données, les ECS -- c'est toute la thématique Array of Structs VS Struct of Arrays.

                  - Todd Mytkowicz et al. ‘Producing Wrong Data without Doing Anything Obviously Wrong !’ In : Proceedings of the 14th International Conference on Architectural Support for Programming Languages and Operating Systems. ASPLOS XIV. Washington, DC, USA : Association for Computing Machinery, 2009, p. 265-276. isbn : 9781605584065. doi : 10.1145/1508244.1508275. url : https://doi.org/10.1145/1508244.1508275.
                  - Brice Adelstein-Lelbach. CppCon 2015 : "Benchmarking C++ Code". https://www.youtube.com/watch?v=zWxSZcpeS8Q. 2015.
                  - Emery Berger. CppCon 2020 : "Performance matters". https://www.youtube.com/watch?v=koTf7u0v41o. Sept. 2020.

                  • Partager sur Facebook
                  • Partager sur Twitter
                  C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                    14 avril 2022 à 14:19:39

                    Alors je pose la question stupide: as-tu essayé de compiler avec l'option -O3 ?
                    J'ai noté des différences significatives.
                    Et moi non plus je ne sais pas pourquoi, mais les temps varient beaucoup sur Windows s'ils sont trop faibles.
                    • Partager sur Facebook
                    • Partager sur Twitter

                    Le Tout est souvent plus grand que la somme de ses parties.

                      14 avril 2022 à 14:58:00

                      Pas besoin de O3, même O1 suffit. C'est déjà inliné: https://gcc.godbolt.org/z/M9nfM6eTr

                      Non, il y a eu y avoir des variations de mesures sur cet exemple, il ne faut pas chercher plus loin --> nanobench la prochaine fois.

                      Et si c'est sur un exemple plus gros la variation, alors pas impossible qu'il y ait eu autre chose (alignement de fonctions?)

                      • Partager sur Facebook
                      • Partager sur Twitter
                      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                        14 avril 2022 à 16:29:19

                        @lmghs

                        Chez moi il y a une différence énorme entre O3 et O1, O1 est au moins 5 fois plus lent. Mais dans tous les cas demeure cette différence de 10 à 20% entre les deux fonctions. Les essais répétés donnent toujours la même chose. C'est peut être mon système MacOS qui fait ça... 

                        • Partager sur Facebook
                        • Partager sur Twitter
                          14 avril 2022 à 16:56:58

                          Ce que je veux dire: dès O1, le code est inliné. Il ne devrait y avoir aucune différence de performances entre les 2 bouts de code pour tout niveau d'optim >= O1.

                          Les seuls écarts observables, sont des écarts de mesure car l'asm est strictement identique!

                          EDIT: au temps pour moi. Il faut attendre clang 13 (le compilo sur mac) pour que l'inlining s'opère en O1: https://gcc.godbolt.org/z/bbMr3oGKv

                          EDIT2: Avec rng.h qui est planqué là https://gist.github.com/LucHermitte/3934a9d436cbf513866f8aa2eb329fb8#file-rng-hpp

                          Et avec

                          #include "rng.h"
                          #include <boost/align/aligned_allocator.hpp>
                          #include <vector>
                          
                          #if defined(__arm__)
                          constexpr bool isArm = true;
                          #else
                          constexpr bool isArm = false;
                          #endif
                          constexpr std::size_t hardware_destructive_interference_size = isArm ? 64 : 128;
                          // static_assert(hardware_destructive_interference_size >= max_align_v, "math?");
                          
                          #if defined(LEVEL1_DCACHE_LINESIZE)
                          constexpr std::size_t hardware_constructive_interference_size = LEVEL1_DCACHE_LINESIZE;
                          #else
                          constexpr std::size_t hardware_constructive_interference_size = 64;
                          #endif
                          
                          template <typename T, std::size_t alignment = hardware_constructive_interference_size>
                          using aligned_vector
                          = std::vector<T, boost::alignment::aligned_allocator<T, alignment>>;
                          
                          constexpr auto avail_L1 = LEVEL1_DCACHE_SIZE / 4;
                          
                          template <typename T, typename I=T>
                          aligned_vector<T> random_vector(int count)
                          {
                            aligned_vector<T> v; v.reserve(count);
                            for (auto i : RNG<I>(count)) {
                                v.emplace_back(i);
                            }
                            return v;
                          }
                          
                          struct Direct {
                              Direct(long i) : a{i}{}
                              long a;
                          };
                          
                          struct Indirect {
                              Indirect(long i) : a{i}{}
                              long get() const { return a; }
                          private:
                              long a;
                          };
                          
                          template <typename Cont> long direct(Cont const& c) {
                              long r = 0;
                              for (auto e : c) {
                                  r += e.a;
                              }
                              return r;
                          }
                          
                          template <typename Cont> long indirect(Cont const& c) {
                              long r = 0;
                              for (auto e : c) {
                                  r += e.get();
                              }
                              return r;
                          }
                          
                          #define ANKERL_NANOBENCH_IMPLEMENT
                          #include <nanobench.h>
                          
                          template <typename T, typename Acc>
                          void bench_acc(ankerl::nanobench::Bench& bench, char const* name, Acc acc)
                          {
                            // int const bytes = 1 << bytes_shift;
                            int const bytes = avail_L1;
                            int const count = bytes / sizeof(T);
                            auto const v = random_vector<T, long>(count);
                          
                            bench.run(name, [&]() {
                                    auto sum = acc(v);
                                    ankerl::nanobench::doNotOptimizeAway(sum);
                                    });
                          }
                          
                          int main()
                          {
                              ankerl::nanobench::Bench b;
                              b.title("Getter VS direct")
                                  .unit("long")
                                  .warmup(100)
                                  .minEpochIterations(3000)
                                  .relative(true);
                              b.performanceCounters(true);
                          
                              bench_acc<Direct>(b, "Direct access", [](auto const& c){return direct(c);});
                              bench_acc<Indirect>(b, "Indirect access", [](auto const& c){return indirect(c);});
                          }
                          


                          J'obtiens après compilation avec

                          g++ -std=c++14 -O3 -DNDEBUG -march=native -DLEVEL1_DCACHE_SIZE="$(getconf LEVEL1_DCACHE_LINESIZE)" nanobench-getter.cpp
                          

                          ceci

                          | relative |             ns/long |              long/s |    err% |     total | Getter VS direct
                          |---------:|--------------------:|--------------------:|--------:|----------:|:-----------------
                          |   100.0% |                2.79 |      358,197,401.16 |    0.1% |      0.00 | `Direct access`
                          |   100.1% |                2.79 |      358,440,557.08 |    0.1% |      0.00 | `Indirect access`
                          


                          Avec clang10 et O1 sur ma machine toujours

                          | relative |             ns/long |              long/s |    err% |     total | Getter VS direct
                          |---------:|--------------------:|--------------------:|--------:|----------:|:-----------------
                          |   100.0% |               27.85 |       35,904,474.33 |    0.2% |      0.00 | `Direct access`
                          |    94.8% |               29.38 |       34,041,589.99 |    0.3% |      0.00 | `Indirect access`
                          


                          EDIT42: En 02 et O3, le second est toujours 114% quelque soit celui que j'exécute en second. Avec g++ en O2, le second est toujours à 75%. Alors que l'on sait (merci godbolt!) que c'est exactement la même fonction (inlinée) d'accumulation.
                          Ca sent furieusement les perturbations d'alignement. Je ne sais plus quelles sont les options pour régler ça.

                          -
                          Edité par lmghs 14 avril 2022 à 19:47:30

                          • Partager sur Facebook
                          • Partager sur Twitter
                          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                            14 avril 2022 à 23:15:23

                            Salut,

                            je rejoins la réponse de lmghs, on ne peut pas tester la performance d'un programme avec un seul PC et encore moins avec qu'un même  OS, car il y a beaucoup des paramètres qui entre en jeux (lmghs a cité quelques uns).

                            déclarer une fonction avec le mot-clé inline est juste une demande qu'un développeur fait au près du compilateur et rien ne garantie que ce dernier sera d'accord (petite fonction ou pas).

                            Umbre37 a écrit:

                            Cela n'a pas de lien directe, mais je trouve que la complexité du c++ est parfois décourageante. 

                            Le C++ comporte des choses parfois obscure

                            -
                            Edité par EL-jos 14 avril 2022 à 23:15:39

                            • Partager sur Facebook
                            • Partager sur Twitter

                            Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .

                              15 avril 2022 à 0:38:50

                              @lmghs Merci pour ton poste très éclairant ! Je ne maîtrise pas autant d'outils que toi bien sûr, mais cela me permet de mieux comprendre. Je devrais avoir le réflexe de comparer l'assembleur, effectivement si le code généré est le même, la comparaison des perfs n'a plus de sens... Les écarts viennent d'autre chose. Je ne comprends pas comment une histoire d'alignement pourrait engendrer ces écarts, il n'y a que des "long" ici. Tout est aligné pareil, non ?

                              @EL-jos Bien d'accord avec toi, je fais les tests comme je peux, avec mon matériel, sur mon temps libre. Je ne suis pas un pro et j'atteins vite mes limites :)

                              Encore merci à tous, je suis heureux d'apprendre des choses grâce à vous ! Bonne soirée !

                              -
                              Edité par Umbre37 15 avril 2022 à 1:00:54

                              • Partager sur Facebook
                              • Partager sur Twitter
                                15 avril 2022 à 0:57:02

                                Umbre37 a écrit:

                                Je ne comprends pas comment une histoire d'alignement pourrait engendrer ces écarts, il n'y a que des "long" ici. Tout est aligné pareil, non ?

                                Lmghs ne parle pas de l'alignement des données en mémoire. Je ne suis pas sûr du terme qu'il utilise "perturbations d'alignement" (en faisant des recherches, tu devrais trouver le terme exacte), mais l'idée est que le premier test laisse l'ordinateur dans un état différents après son exécution (fetching des instructions et données par exemple) et que cela influence les performances du second tests.

                                Faire des tests et benchmarks est en réalité un domaine complexe, il y a pleins de pièges à connaître et il faut savoir interpréter correctement. Il existe plein de littérature sur le sujet.

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  15 avril 2022 à 1:16:36

                                  Je pensais essentiellement à l'alignement du code en mémoire. Pas des données. Enfin... le code est aussi une donnée. Par exemple si une boucle est à cheval sur 2 pages de codes qui nécessite des rafraîchissements réguliers du cache d'instructions, on peut facilement perdre des perfs pour des raisons très difficiles à comprendre/anticiper.

                                  Il y a possiblement d'autres aspects à l'oeuvre. Coté état de l'art, les 2 principales entrées étaient Mytkowicz et Berger si mes souvenirs sont bons. J'avais donné les références dans mon premier message.

                                  Après il y a aussi des aspects statistiques qui me dépassent complètement pour être capable de statuer s'il y a un écart suffisant entre les mesures de 2 séries d'expériences -- (p-value, t-test de Student)  le genre de truc qui est aussi censé être utilisé pour déterminer si un médoc a plus d'effet qu'un placebo, et bien d'autres choses si mes souvenirs sont bons... Je ne sais plus la, ou les, quelle(s) des 3 entrées biblio que j'avais données en parle.

                                  > déclarer une fonction avec le mot-clé inline est juste une demande qu'un développeur fait au près du compilateur et rien ne garantie que ce dernier sera d'accord (petite fonction ou pas).

                                  Pire, au sein d'une même unité de traduction, le compilateur peut inliner du coder sans qu'on lui demande -- cela s'observe très régulièrement sur godbolt et demande un peu d'attention pour observer ce qui nous intéresse. Et je soupçonne que la phase de LTO (Link Time Optimization) va avoir des effets similaires -- je ne maitrise pas trop ce sujet.

                                  -
                                  Edité par lmghs 15 avril 2022 à 1:18:42

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                    15 avril 2022 à 1:58:00

                                    Alors je m'avance sur un terrain que je ne connais pas et ne peux résoudre facilement (voir mon profil).
                                    Je pose tout de même naïvement la question. Comment vérifie-t-on la collision entre des figures géométriques?
                                    Est-ce qu'on fait un balayage de chaque ligne de pixels ou si on décompose les figures en triangles et on essaie de savoir si un point d'une figure est dans un des triangles de l'autre?
                                    Parce que le balayage coûterait très cher.
                                    Il y a des choses sur le web à propos de ce problème.
                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    Le Tout est souvent plus grand que la somme de ses parties.

                                      15 avril 2022 à 2:12:44

                                      @PierrotLeFou

                                      La question que je pose ici et les tests proposés n'ont pas de rapport direct avec mes choix de conception. Mon seul but ici était de comparer dans le cas général l'accès aux données avec/sans accesseurs. Pour moi la question est réglée : le code assembleur généré est le même, donc c'est strictement équivalent en -O2 ou -O3.

                                      Pour les collisions d'après mes recherches, les solutions les plus communes pour réduire la complexité sont le quad-tree, la grid et le sweep and prune. Mais j'ai eu une autre idée (originale je crois, je ne l'ai trouvée nul part) qui fonctionne bien. J'essaie maintenant d'améliorer les perfs au maximum. J'ai déjà obtenue des gains significatifs. Je cherche maintenant à grappiller encore quelques FPS.

                                      @lmghs @gbdivers

                                      Ok pour l'alignement, je ne connaissais pas ce problème. Merci de ces précisions.

                                      -
                                      Edité par Umbre37 15 avril 2022 à 2:19:46

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        15 avril 2022 à 2:44:50

                                        PierrotLeFou a écrit:

                                        Comment vérifie-t-on la collision entre des figures géométriques?
                                        Est-ce qu'on fait un balayage de chaque ligne de pixels ou si on décompose les figures en triangles et on essaie de savoir si un point d'une figure est dans un des triangles de l'autre?

                                        Il existe pleins de techniques. Ca peut être fait directement avec des maths, il existe des algos spécifiques pour détecter les intersections de triangles. Je conseille le livre Mathematics for 3D Game Programming and Computer Graphics. Il existe également des algos d'intersections ligne-triangle qu'on peut utiliser avec des approches de ray tracing. Ou encore les techniques de color picking (chaque triangle est rendu avec une couleur différente et on regarde la couleur du pixel qui nous intéresse). Et effectivement, comme dit Umbre37, c'est utilisable avec des méthodes de partitionnement de l'espace.

                                        C'est le genre de sujet qu'il faut regarder dans la littérature, c'est un domaine actif, il doit y avoir des methodes, nouvelles ou pas, que je ne connais pas (je suis pas spécialiste).

                                        -
                                        Edité par gbdivers 15 avril 2022 à 2:46:27

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          15 avril 2022 à 11:34:38

                                          Umbre37 a écrit:

                                          La question que je pose ici et les tests proposés n'ont pas de rapport direct avec mes choix de conception. Mon seul but ici était de comparer dans le cas général l'accès aux données avec/sans accesseurs. Pour moi la question est réglée : le code assembleur généré est le même, donc c'est strictement équivalent en -O2 ou -O3.


                                          Je suis un peu perplexe là dessus, par ce que quand le Mr (compilateur) voit un accesseur, il fera  un appel à la fonction (ici ton accesseur), or qui dit appel à la fonction dit recalcule du corps de la fonction ce qui est couteux, nous pouvons observer cet impact dans des technos qui s'exécute côté client (navigateur) comme TypeScript pour Angular qui font aussi de la compilation du code en JavaScript; C'est la raison pour laquelle le JavaScript permet l'accès aux attributs avec ou sans accesseurs.

                                          Note: Tu te poseras peut-être la question de l'existence de la technique des accesseurs ? à mon humble avis c'est pour respecter les règles de la POO, mais rien ne t'y oblige, car nous tous lors du développement, nous ne respectons pas à 100% les règles de la POO pire encore de la norme. 

                                          -
                                          Edité par EL-jos 15 avril 2022 à 11:36:36

                                          • Partager sur Facebook
                                          • Partager sur Twitter

                                          Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .

                                            15 avril 2022 à 11:40:26

                                            Je conseille également le livre "Real-Time Collision Detection", mais surtout "Physically Based Rendering, From Theory to Implementation" pour ses implémentations et le souci d'optimisation des structures accélératrices (BVH et k-d tree surtout).

                                            -
                                            Edité par SpaceIn 15 avril 2022 à 11:40:34

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              15 avril 2022 à 11:53:10

                                              PierrotLeFou a écrit:

                                              Alors je m'avance sur un terrain que je ne connais pas et ne peux résoudre facilement (voir mon profil).
                                              Je pose tout de même naïvement la question. Comment vérifie-t-on la collision entre des figures géométriques?
                                              Est-ce qu'on fait un balayage de chaque ligne de pixels ou si on décompose les figures en triangles et on essaie de savoir si un point d'une figure est dans un des triangles de l'autre?
                                              Parce que le balayage coûterait très cher.
                                              Il y a des choses sur le web à propos de ce problème.


                                              Si on part sur le calcul de collisions, ça m'intéresse ! 

                                              Mais je pense qu'il faudrait alors créer un autre topic !

                                              • Partager sur Facebook
                                              • Partager sur Twitter

                                              Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                                                15 avril 2022 à 13:09:43

                                                > Note: Tu te poseras peut-être la question de l'existence de la technique des accesseurs ? à mon humble avis c'est pour respecter les règles de la POO, mais rien ne t'y oblige, car nous tous lors du développement, nous ne respectons pas à 100% les règles de la POO pire encore de la norme. 

                                                Pas d'accord. C'est beaucoup de totale non compréhension de la conception OO. Quand on pense OO, on pense aux services. Pas aux données que l'on va exposer.

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                                  15 avril 2022 à 13:50:56

                                                  juste pour papoter:

                                                  lmghs a écrit:

                                                  Quand on pense OO, on pense aux services. Pas aux données que l'on va exposer.


                                                  Du coup, on peut ou n'est pas implémenté des accesseurs vu qu'on pense pas aux données qui seront exposés:p
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter

                                                  Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .

                                                    15 avril 2022 à 14:01:38

                                                    (je ne comprends pas la phrase)
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                    C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                                      15 avril 2022 à 14:11:07

                                                      En faite je voulais dire, vu qu'on pense service et non à l'exposition des données, alors pourquoi l'existence de l'encapsulation ?

                                                      -
                                                      Edité par EL-jos 15 avril 2022 à 14:11:25

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter

                                                      Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .

                                                        15 avril 2022 à 14:12:17

                                                        EL-jos a écrit:

                                                        Je suis un peu perplexe là dessus, par ce que quand le Mr (compilateur) voit un accesseur, il fera  un appel à la fonction (ici ton accesseur), or qui dit appel à la fonction dit recalcule du corps de la fonction ce qui est couteux

                                                        En fait, c'est beaucoup moins couteux que l'on ne pourrait le croire si l'on s'adresse directement au processeur, ce que fait le C++ (vu qu'il s'agit d'un langage compilé).

                                                        Car, au niveau du processeur, l'entrée et la sortie d'une fonction se font en quelques instructions à  peine: en comptant ce qui est fait (au niveau de la fonction appelante) pour préparer l'appel de la fonction et ce qui est fait (au  niveau de la fonction appelée) pour permettre à la fonction appelante de récupérer le résultat (s'il y en a un), il n'y a même pas une dizaine d'instructions effectuées (sub, mov call pour l'appel, pop, mov, ret pour la sortie).

                                                        Cela se fait en à  peine "quelques ticks d'horloges" ;)

                                                        EL-jos a écrit:

                                                        nous pouvons observer cet impact dans des technos qui s'exécute côté client (navigateur) comme TypeScript pour Angular qui font aussi de la compilation du code en JavaScript; C'est la raison pour laquelle le JavaScript permet l'accès aux attributs avec ou sans accesseurs.

                                                        Principalement parce que ce sont des langages interprétés: avant d'atteindre ce moment où ces instructions seront effectivement transmises au processeur, il faut

                                                        • commencer par parser le code
                                                        • l'analyser pour s'assurer qu'il est correct et pour déterminer que l'on a affaire à  l'appel d'une fonction
                                                        • déterminer quelle fonction sera appelée
                                                        • convertir le tout en code exécutable par le processeur
                                                        • s'assurer que le processeur effectue ces instructions

                                                        Oui, je verse dans la caricature. Et encore ;)

                                                        Ce qui importe vraiment dans l'histoire, c'est que ce n'est pas le cout de l'appel de la fonction qui est à mettre en cause, mais bien le cout de tout ce qui doit être fait afin de permettre effectivement que l'appel ait lieu (dans un magasin, nous dirions que ce n'est pas tant le cout de la marchandise qui la rend chère, mais bien le cout de son transport ;) )

                                                        EL-jos a écrit:

                                                        Note: Tu te poseras peut-être la question de l'existence de la technique des accesseurs ? à mon humble avis c'est pour respecter les règles de la POO, mais rien ne t'y oblige, car nous tous lors du développement, nous ne respectons pas à 100% les règles de la POO pire encore de la norme.

                                                        En fait, les accesseurs sont surtout la preuve que la personne qui les emploie n'a absolument rien compris au principe de l'encapsulation.

                                                        Il ne sert à  rien de limiter l'accès direct à  une donnée si c'est pour -- d'un autre coté -- permettre à l'utilisateur d'aller en modifier la valeur sans le moindre contrôle au travers d'un accesseur.

                                                        D'abord, cela contrevient à la loi de Déméter, mais surtout, cela ne nous apporte absolument pas la garantie que l'utilisateur fournira une valeur cohérente et valide pour cette donnée.

                                                        Si on veut encapsuler correctement une donnée, il faut s'assurer que toutes les modifications que cette donnée pourrait subir et tous les calculs menant à ces modifications soient "validés" et exécutés par une fonction qui connaisse parfaitement les "limites acceptables" de la valeur de cette donnée.

                                                        De plus, l'encapsulation n'est absolument pas  ni un principe issu ni un principe fondateur de la programmation orientée objets.  Ce principe était connu et appliqué bien avant l'arrivé de ce "nouveau paradigme" ;).

                                                        J'en veux pour preuve la structure FILE que l'on retrouve en C, qui est parfaitement encapsulée (je te mets au défi de me dire de quelles données elle est composée) et dont la manipulation passe par "un certain nombre" de fonctions (fopen, fread, fwrite et autres ) qui s'assurent que ses données internes soient toujours cohérentes.

                                                        Si tu te demande alors quel est le véritable principe "fondateur" de la programmation orientées objets, sache qu'il s'agit en réalité de la substituabilité, au termes du principe de substitution de Liskov: la possibilité de transmettre une donnée de type B à une fonction qui s'attend à manipuler une donnée de type A en faisant passer notre donnée de type B pour une donnée de type A ;)

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                                          15 avril 2022 à 14:21:15

                                                          koala01, toujours aussi clair  dans tes propos :)
                                                          cela m'aurait étonné que koala01 ne nous rédige pas un mini livre là  où  deux icones du C++ francophone (gbdivers et lmghs ont dit quelque chose).
                                                          J'attend plus que la réaction  de Ksass`Peuk :-°

                                                          -
                                                          Edité par EL-jos 15 avril 2022 à 14:27:53

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter

                                                          Ton présent détermine ton futur et la connaissance te placera au dessus de ta génération .

                                                            15 avril 2022 à 15:39:27

                                                            EL-jos a écrit:

                                                            En faite je voulais dire, vu qu'on pense service et non à l'exposition des données, alors pourquoi l'existence de l'encapsulation ?


                                                            Je ne fais pas sens de la question.

                                                            J'ai une appréciation subtilement différente de la présentation Koala01.

                                                            D'abord la notion d'abstraction. C'est là que l'on s'intéresse aux services rendus. OSEF des détails, on les cache. Ça fait super longtemps que l'on a ça. En C, ça commence avec FILE.

                                                            Après, il y a l'encapsulation. Mon appréciation est que je mets une capsule protectrice autour de choses sensibles qu'il convient de protéger d'interférences externes. La notion d’invariant est particulièrement centrale.

                                                            Après il y a les diverses (il n'y en a pas qu'une. Loin de là!) façons de mettre en oeuvre ces principes en C++, Python, Eiffel...

                                                            La frontière entre abstraction et encapsulation est très floue dans le monde OO tant on les utilise ensemble au point de ne plus les distinguer parfois, surtout quand on emploie la même technique pour un peu tout faire.

                                                            Je me suis déjà étendu moult fois à divers endroits. Je ne sais pas s'il y a un endroit plus complet qu'un autre. https://www.developpez.net/forums/d1549658-7/general-developpement/langages-programmation/pourriez-expliquer-l-oriente-objet/#post8426069 (ou peut etre perdu au milieu des discussions sur la préparation du big tuto de C++ de ZdS?)

                                                            Si je mets en avant les données, même si je n'ai que des getters qui vont garantir des invariants (et participer à une encapsulation), je ne vais pas penser service. Je pense à ce que je stocke pour les autres. Pas à ce que je peux faire pour eux. Ce n'est pas le/un fondamental de la pensée OO. Et si en plus je n'ai pas le moindre invariants (ex. sur des coordonnées de points 3D), alors quel sens de mettre des accesseurs et mutateurs. Si je n'ai pas d'invariant, je n'ai pas à faire semblant de faire de l'OO: d'autant qu'exposer les données tend à s'accompagner d'un appauvrissement des services associés.

                                                            -
                                                            Edité par lmghs 15 avril 2022 à 15:51:13

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                            C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.

                                                            Faut-il éviter les appels de fonction ?

                                                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                            • Editeur
                                                            • Markdown