Journal Entretient du noyau Linux

Posté par  (site web personnel) .
Étiquettes : aucune
0
20
déc.
2004
Récemment j'ai fait la lecture d'un article de recherche [1] qui proposait une analyse des couplage communs (common coupling) dans le noyau de Linux. Le but de l'article fût de définir une méthode de classification des couplage communs présents dans les logiciels à base de noyau. Ces classifications fût basées sur le caractéristique "definition-use" des références aux variables global.

Suite à l'analyse du noyau (Version 2.4.20), les auteurs ont retrouvé 1908 définitions de variables global classifier comme étant "non-sécuritaires". Selon eux, ceci indique que des problèmes se présentent à l'horizon en ce qui concerne l'entretient du noyau Linux a moins que le nombre de couplage communs soient réduit.

Une question que je me suis posé d'ailleurs lors de la lecture est comment ces résultats comparent avec ceux qui pourrais être obtenus avec le noyau d'un système propriétaire (Windows étant l'exemple le plus évident). Y-a-t'il peut être quelqu'un qui fréquente DLFP qui aurait des intuitions à ce sujet?

[1] Yu et al., Categorization of Common Coupling and its Application to the Maintainability of the Linux Kernel, IEEE Transactions on Software Engineering, Vol. 30, No. 10, Octobre, 2004 (http://www.ise.gmu.edu/faculty/ofut/rsrch/abstracts/srs-dumaint.htm(...)).
  • # HEU...

    Posté par  . Évalué à 10.

    S'il-te-plaît, pourrais tu t'exprimer dans une langue qui soit compréhensible aux non-experts en analyse de code ?
    analyse des couplage communs (common coupling) : késako ?
    Ces classifications fût basées sur le caractéristique "definition-use" des références aux variables global. : idem

    Merci d'avance
    • [^] # Re: HEU...

      Posté par  . Évalué à 2.

      Et puis si en plus c'était copréhensible pour les personnes qui parlent le français classique.

      Je repasse à quelle heure ?
    • [^] # Re: HEU...

      Posté par  (site web personnel) . Évalué à 2.

      Pour répondre a vos questions. Le couplage communs (c'est ma traduction pour "common coupling" et ce n'est peut-être pas tout-a-fait juste) est une mesure du degré d'intéraction entre deux unitées d'un système logiciel, et donc la dépendance entre ces unitées.

      Le caratéristique "definition-use" peut être résumé comme suit: si une variable est modifiée, c'est une définition (donc caractéristique "definition"). Si la même variable est utilisée en lecture (sans la modifié), c'est une utilisation simple de la variable (donc caractéristique "use").

      Donc l'analyse "definition-use" fait l'analyse des définitions et utilisations simples des variables globals et du contexte de ces utilisations. Par exemple, une variable peut être définit dans un module du noyau et simplement utilisée dans un module à l'extérieure du noyau etc.

      Désoler si le journal n'était pas claire. Je l'ai posté au travail et j'ai été presser lors de sa rédaction.
      • [^] # Re: HEU...

        Posté par  . Évalué à 3.

        Désoler si le journal n'était pas claire.
        Claire ? Tu parles de Marie-Claire.

        ---
        Il est où ce p... de -1
        • [^] # Re: HEU...

          Posté par  (site web personnel) . Évalué à 2.

          Ouf, effectivement c'est faux. J'aimerais dire que c'est une faute de frappe, mais j'avoue que c'est une faut d'inattention.
  • # Hmm :/

    Posté par  (site web personnel) . Évalué à -2.

    je dirais que à mon avis Windows est surement moins touché par ce probleme <troll> car les gens de chez microsoft savent coder, eux!</troll>

    Nan, il faudrait surtout comparer avec un noyau bsd histoire de voir.
    Voir aussi quelle partie du code a été analysée(coeur, drivers, ...)

    Mais 1908 variables globales, ca me fait triper!

    Je commence a me demander vraiment pkoi des gens aussi bon en info peuvent se permettre de coder comme des porc en ne respectant meme pas les regles de base que on apprend lors des premiers cours de programmation. En 3 ans d'iut, j'ai jamais utilisé une variable globale dans un seul programme et jamais nos prof aurait laissé ca passer. Seul notre prof de systeme avait tendance a en abuser par facilité et je me demande si ce n'est pas ce qui arrive à linux :(
    • [^] # Re: Hmm :/

      Posté par  . Évalué à 10.

      On m'a dit aussi qu'en programmation un "goto" c'etait pas bien. Et d'ailleur mes profs d'infos ne m'auraient pas pardonne de les utiliser.

      Cependant tu pourras lire des ecrits de Knuth te disant que dans certain cas, c'est tout a fait acceptable et c'est d'ailleur la meilleure des solutions.

      Tout ca pour dire, sans vouloir reduire la qualite de tes cours, qu'il est parfois beaucoup plus simple d'interdire l'utilisation d'une fonctionnalite plutot que d'enseigner le cadre idoine de son utilisation.

      Pour en revenir aux variables globales, on peut les considerer dangereuses si on utilise les criteres ennonces dans l'article en lien. Cependant il faut avouer qu'au dela de l'aspect simplicite, l'acces en lecture/ecriture est beaucoup plus rapide.
      • [^] # Re: Hmm :/

        Posté par  . Évalué à 4.

        On m'a dit aussi qu'en programmation un "goto" c'etait pas bien.

        et pourquoi c'est pas bien? Je me suis toujours demandé d'où vient cette allégation.
        • [^] # Re: Hmm :/

          Posté par  (site web personnel) . Évalué à 4.

          C'est une question de lisibilité du code.

          Si tu prends un bouquin à n'importe quelle page, tu peux lire sans trop de difficultés, tu vois où tu en es dans le bouquin (au début/milieu/fin) etc.

          Si tu lis un bouquin dont vous êtes le héros, c'est déjà plus difficile.

          Le GOTO représente cet aspect. Il augmente le risque d'erreurs et diminue la clarté à la relecture.

          Il faut aussi savoir que plus un programme est gros, plus le nombre de bug est important. Donc, un GOTO "peut" ne pas représenter de probleme dans le cas d'un petit programme, mais dans le cas d'un gros probleme c'est vraiment problématique.

          Axel
          • [^] # Re: Hmm :/

            Posté par  . Évalué à 10.

            bof ... de tels arguments me font marrer ! on utilise de plus en plus de langages objets qui utilisent beaucoup le mécanisme des exceptions (ce dont je me félicite d'ailleurs, la qualité et la lisibilité y gagnent, justement). Or dans le cas du C++ par exemple (je prend celui-ci car c'est un langague OO très utilisé) les exceptions ont à peu près toutes les caractéristiques que tu décris et qui semblent poser problème dans le goto.
            Alors en C, 'goto' c'est mal. Mais en C++, les exceptions c'est bien. Mouarf :)
            Les goto pour la gestion des erreurs sont bien plus puissants, lisibles et compréensibles qu'un break/continue/return posé au milieu de 3 boucles imbriquées ; et puis, comme pour les exceptions (mais dans un moindre mesure) ça permet de factoriser le code de traitement d'une erreur.

            bref, je ne suis toujours pas convaincu.
            • [^] # Re: Hmm :/

              Posté par  (site web personnel) . Évalué à 6.

              Comme l'indique leur nom les exception sont "exceptionnelles" et ne doivent en aucun cas être utilisé comme une possibilité de transmission de message comme une autre.

              Même si elles peuvent apparaîtrent n'importe où, elles ne permettent pas de se brancher n'importe où dans le code contrairement au goto, qui de plus peu être utilisé à tout va puisque son utilisation n'est pas limité par sa sémantique.

              L'exemple de la programmation objet comme tu dis est un parfait exemple de style de programmation où le goto devient complètement inutile et déconseillé : les méthodes sont réduites à leur plus simple expression, ne font qu'un dizaine de ligne maximum, alors l'intérêt d'un goto là dedans... Et si tu uitlises un goto pour sortir d'une méthode tu te retrouves avec du code spaghetti indéboggable et tu te fais virer par ton chef de projet qui fait de la programmation par aspect ete qui ne captes pas pourquoi son code à lui n'est jamais exécuté...

              Les goto pour la gestion des erreurs sont bien plus puissants, lisibles et compréensibles qu'un break/continue/return posé au milieu de 3 boucles imbriquées ;
              Ah bon tu remplacent le mot break/continue/return par un goto et tu comprends mieux ? Je captes pas... ok y'a moins de lettres... break/continue/return sont des cas particulier de goto, ont une sémantique et une utilisation précise qui fait que tout le monde les comprend, notamment le compilateur (et c'est pas négligeable).
              • [^] # Re: Hmm :/

                Posté par  . Évalué à 4.

                http://kerneltrap.org/node/553(...)

                > un goto et tu comprends mieux ?

                Si c'est dans deux boucles evidement.

                De même pour gérer les erreurs un goto permet d'ameillorer la lisibilité en déplacant le code de gestion d'erreur en dehors du code lui même et allege la maintenance car il evite de dupliquer les actions effectuées lors de la gestion d'erreur.

                Ce qui me fait marrer c'est qu'on sort toujours l'exemple du goto degeulasse qui saute dans une autre fonction pour expliquer que le goto est mauvais. Mais il me semble que personne de nos jours n'a l'intention de l'utiliser ainsi. Donc plutôt que de parler sur des cas qui n'existe pas, prouve moi qu'il y a plus elegant que le goto pour l'usage qu'on en fait dans la vie reelle.

                Tu trouveras dans l'archive google du thread resumé par kerneltrap plusieurs exemples de code. Tu verras aussi que le mec qui a proposé de virer les goto a mis *UN* exemple et qu'il s'est completement vautré et que son code ne marche pas... Plus simple et plus propre on avait dit....
              • [^] # Re: Hmm :/

                Posté par  (site web personnel) . Évalué à 7.

                contrairement au goto, qui de plus peu être utilisé à tout va puisque son utilisation n'est pas limité par sa sémantique.

                goto est limité à la fonction locale.

                Ah bon tu remplacent le mot break/continue/return par un goto et tu comprends mieux ? Je captes pas... ok y'a moins de lettres... break/continue/return sont des cas particulier de goto, ont une sémantique et une utilisation précise qui fait que tout le monde les comprend, notamment le compilateur (et c'est pas négligeable).

                Prends une fonction quelconque qui alloue plusieurs trucs (c'est courant dans le kernel) et qui vérifie les allocations en question (c'est pas courant ailleurs que dans le kernel).

                ma_fonction() {
                resultat = NULL;
                if ((truc_a = malloc(stuff)) == NULL)
                return NULL;
                if ((truc_b = malloc(stuff)) == NULL) {
                free(truc_a);
                return NULL;
                }
                if ((truc_c = malloc(stuff)) == NULL) {
                free(truc_a);
                free(truc_b);
                return NULL;
                }

                if ((truc_d = malloc(stuff)) == NULL) {
                free(truc_a);
                free(truc_b);
                free(truc_c);
                return NULL;
                }

                if (test-je-veux-un-lock == PAS_LOCK) {
                free(truc_a);
                free(truc_b);
                free(truc_c);
                free(truc_d);
                return NULL;
                }

                /* fait des trucs ... */
                rend_le_lock();
                free(truc_d);
                free(truc_c);
                free(truc_b);
                free(truc_a);
                return RESULTAT;
                }

                Tu as aussi la méthode avec de multiples if()s imbriqués, totalement illisible puisque le cas échéant, ton code principal sera 4 niveaux plus bas et ton code de gestion d'erreur prendra toute la place.

                Maintenant prends la version gotoisée:
                ma_fonction()
                {
                resultat = NULL;
                if ((truc_a = malloc(stuff)) == NULL)
                goto bail;

                if ((truc_b = malloc(stuff)) == NULL)
                goto bail_a;

                if ((truc_c = malloc(stuff)) == NULL)
                goto bail_b;

                if ((truc_d = malloc(stuff)) == NULL)
                goto bail_c;

                if (test-je-veux-un-lock == PAS_LOCK)
                goto bail_d;

                /* fait des trucs ... */

                rend_le_lock();

                bail_d:
                free(truc_d);
                bail_c:
                free(truc_c);
                bail_b:
                free(truc_b);
                bail_a:
                free(truc_a);

                return RESULTAT;
                }


                Tu veux une explication de l'intérêt de la chose? Ou tu as compris le truc?

                Les profs t'apprennent que les variables globales c'est mal, que les goto c'est mal, etc, parce qu'ils t'apprennent mal.
                • [^] # Re: Hmm :/

                  Posté par  (site web personnel) . Évalué à 4.

                  void * trucs[4];

                  int i = 0;

                  while(i<4){ //ALLOC
                  truc[i] = malloc(stuff);
                  if(truc[i] == null) break;
                  i++;
                  }

                  if(i<4) //ERROR ALLOC
                  while(i>0){
                  i--;
                  free(trucs[i]);

                  }

                  u veux une explication de l'intérêt de la chose? Ou tu as compris le truc?
                  Désolé, aux erreurs de programmation prête il y a toujours moyen de faire autrement, et là en l'occurence c'est encore plus court ;)

                  Les profs t'apprennent que les variables globales c'est mal, que les goto c'est mal, etc, parce qu'ils t'apprennent mal.
                  Nan c'est juste que dans 95% des cas c'est mal et qu'il faut toujours essayer de s'en passer (ça ne coûte rien d'essayer)

                  Evidemment tu trouveras sûrement un cas où c'est pratique/concis/etc. mais les profs étaient là pour nous expliquer les dangers du schmurck, alors dire qu'ils apprennent mal c'est un peu fort. C'est pas tous les jours qu'on code un kernel ;)
                  • [^] # Re: Hmm :/

                    Posté par  (site web personnel) . Évalué à 2.

                    De manière générale j'ai l'impression que l'utilisation du goto dans ce cas précis tend plutôt à montrer que le programmeur ne s'est pas pas rendu compte qu'il gérait une "pile" et qu'elle peut très bien être simulée avec un tableau comme je l'ai fait dans mon exemple.
                  • [^] # Re: Hmm :/

                    Posté par  . Évalué à 4.

                    Passons sur le fait que semantiquement ce n'est pas la meme chose. Tu peux tres bien avoir 2 variables de type different et 2 verrous a prendre. Tiens c'est pas une pile du coup !

                    Allez on passe sur un vrai exemple pratique qui ferait vomir n'importe quel prof de programmation du cote bien de la force.

                    http://fxr.watson.org/fxr/source/kernel/fork.c?v=linux-2.6.9#L904(...)

                    Tu me le recodes copy_process sans goto avec des fonctions qui font pas plus d'un ecran toussa ? Je suis sur que tout le monde sera tres content de ta contribution a rendre le noyau moins degeulasse puisqu'il y a toujours moyen de faire autrement

                    Conditions :
                    - le code doit marcher
                    - le code ne doit pas etre plus compliqué a comprendre
                    - le code ne doit pas etre plus de 10% plus lent

                    Tu as le temps que tu veux :-)
                    • [^] # Re: Hmm :/

                      Posté par  (site web personnel) . Évalué à 1.

                      Oué enfin je suis pas non plus un hacker fou pro du C :)
                      Autant je comprends à peu près l'intérêt lorsqu'on gère une pile (effectivement lorsque les allocations/libérations sont différentes), mais parofis je captes pas trop l'intérêt (notamment lorsque les goto ne s'enchaîne pas :
                      exemple :
                      fork_out:
                      1142 if (retval)
                      1143 return ERR_PTR(retval);
                      1144 return p;

                      pourquoi ne pas mettre ca dans une fonction ? à mon avis le compilateur la inlinera très bien.

                      sinon de manière plus générale j'ai envie de faire :

                      gestion_erreur(code){
                      switch(code){
                      case ERROR_ALLOC_TRUC:
                      do_stuff();
                      case ...

                      }
                      }

                      celà cache implicitement des gotos mais c'est déjà plus élégant que la suite :

                      ad_fork_cleanup_namespace:
                      1147 exit_namespace(p);
                      1148 bad_fork_cleanup_mm:
                      1149 if (p->mm)
                      1150 mmput(p->mm);
                      1151 bad_fork_cleanup_signal:
                      1152 exit_signal(p);
                      1153 bad_fork_cleanup_sighand:
                      1154 exit_sighand(p);
                      1155 bad_fork_cleanup_fs:
                      1156 exit_fs(p); /* blocking */
                      1157 bad_fork_cleanup_files:
                      1158 exit_files(p); /* blocking */
                      1159 bad_fork_cleanup_semundo:
                      1160 exit_sem(p);
                      1161 bad_fork_cleanup_audit:
                      1162 audit_free(p);
                      1163 bad_fork_cleanup_security:
                      1164 security_task_free(p);
                      1165 bad_fork_cleanup_policy:
                      • [^] # Re: Hmm :/

                        Posté par  (site web personnel) . Évalué à 4.


                        1142 if (retval)
                        1143 return ERR_PTR(retval);
                        1144 return p;

                        pourquoi ne pas mettre ca dans une fonction ?


                        Pour que ce soit lisible.

                        à mon avis le compilateur la inlinera très bien.

                        (si on lui dit)


                        gestion_erreur(code){
                        switch(code){
                        case ERROR_ALLOC_TRUC:
                        do_stuff();
                        case ...

                        }
                        }


                        Et tu le mets où ton switch ? Dans une autre fonction ? avec la belle fonction inlinee précédemment ça sous-entend que chaque
                        if(...)
                        goto truc;

                        se transforme en
                        if (...) {
                        gestion_erreur(code, toutes, mes, variables, locales, que, je, traite, dans, le cas, d'une erreur);
                        return ma_belle_fonction_inlinee(retval, p);
                        }

                        Si tu le mets pas dans une autre fonction t'es obligé d'utiliser un goto de toutes façons.

                        C'est tellement plus clair, moins dupliqué, plus lisible, tout ça.
                        • [^] # Re: Hmm :/

                          Posté par  (site web personnel) . Évalué à 2.

                          effectivement c'est peut être plus verbeux mais tellement plus explicite : le nom de la méthode gestion_erreur indique déjà clairement ce qu'on fait : on traite une erreur; avec le goto je ne savais pas du tout ce qu'il y avait au bout du goto et ce qu'il faisait.

                          Après c'est vrai qu'en C y'a ce foutu problème d'absence d'unité d'encapsulation obligeant à mettre les variables en globales si on ne veut pas toutes les passer en paramètre. Une solution pour éviter la lourdeur serait de les encapsuler dans une structure.

                          par contre le "moins dupliqué" je ne suis pas d'accord", le "moins lisible" je dirais plutôt "moins verbeux". Mais si on part de ce principe on code en assembleur, c'est vrai quoi, au moins les instructions ne sont pas verbeuses et on peut mettre des jump partout !

                          Si tu le mets pas dans une autre fonction t'es obligé d'utiliser un goto de toutes façons.
                          ???

                          Enfin comme quoi c'est possible de s'en passer de ces goto, c'est souvent plus verbeux mais on ne perd rien en duplication et pas grand chose en perfs.
                          • [^] # Re: Hmm :/

                            Posté par  . Évalué à 1.

                            > Enfin comme quoi c'est possible de s'en passer de ces goto, c'est souvent plus verbeux mais on ne perd rien en duplication et pas grand chose en perfs.

                            Il faut voir aussi, dans l'exemple de code de copy_process() ci-dessus, que l'utilisation des goto permet de placer une quantité de code ~négligeable pour la gestion des erreurs ("goto xxx", instruction normalement jamais exécutée). Ainsi, le cache d'instructions cpu ne se remplit pas avec du code ~jamais exécuté : on gagne effectivement en performances.
                            Il est d'ailleurs prévu de pouvoir aider le compilateur en lui donnant le résultat probable du test, avec les macros "likely" ou "unlikely". Et ainsi le code s'exécute "tout droit" (mais ce n'est pas fait ici).
                            Linus encourage le goto dans les normes de codage du noyau. Mais bon, un noyau, c'est pas du code "normal", l'exemple est peut-être un peu mal choisi.

                            Pour en revenir au débat, je pense qu'un bon goto n'a pas son pareil, mais qu'il doit être utilisé avec parcimonie. De fait, à part le traitement des erreurs, je n'ai pas d'exemple où un goto est nécessaire.
                            • [^] # Re: Hmm :/

                              Posté par  (site web personnel) . Évalué à 3.

                              Ainsi, le cache d'instructions cpu ne se remplit pas avec du code ~jamais exécuté : on gagne effectivement en performances.

                              Ca ne change strictement rien : dans mon exemple on fait l'appel à la méthode de traitement d'erreur avec autant de probabilité qu'avec un if+goto.

                              Ensuite ce n'est pas au compilateur de se soucier de ça : le processeur gère lui même un cache d'instruction en fonction de la fréquence d'apparition des instructions, et tu ne pourras jamais exécuter le code tout droit, il faut obligatoirement faire les tests de nullitée. Sans compter que de toute façon le processeur évaluera peut être les 2 branches s'il a rien d'autre à foutre (faut bien remplir le pipeline).

                              Ensuite vu que chaque proc y vas de sa méthode de cache, je vois pas trop ce que tu vas indiquer au compilo de faire...

                              Enfin je finirais par faire remarquer que je ne suis pas sûr que l'appel à fork soit si fréquent pour que le proc le mette dans son cache d'instruction...

                              Enfin tout ça pour dire qu'un if+goto ou un if+appel ne change rien au niveau des perfs si la condition n'est quasiment jamais rempli (ce qui est vrai la plupart du temps)
                              • [^] # Re: Hmm :/

                                Posté par  . Évalué à 1.

                                Ensuite ce n'est pas au compilateur de se soucier de ça : le processeur gère lui même un cache d'instruction en fonction de la fréquence d'apparition des instructions, et tu ne pourras jamais exécuter le code tout droit, il faut obligatoirement faire les tests de nullitée. Sans compter que de toute façon le processeur évaluera peut être les 2 branches s'il a rien d'autre à foutre (faut bien remplir le pipeline).

                                Merci. C'est grace a des raisonnements comme ceux la qu'on se retrouve avec du code ultra lent sur Itanium.

                                Pour info dans le cas de l'Itanium c'est bien le compilateur qui s'en soucie et qui va (tenter d')annoncer au processeur "tiens compte du branchement qui suit".

                                En plus je comprend rien a ce que tu dis a propos "de la fréquence d'apparition des instructions". Il etablit des statistiques sur les instructions executee dans le passe et il s'en souvient quand il revient dessus? Tu fonctionnes sur quel model? Parce que la tu m'interesses.
                                • [^] # Re: Hmm :/

                                  Posté par  (site web personnel) . Évalué à 3.

                                  Pour info dans le cas de l'Itanium c'est bien le compilateur qui s'en soucie et qui va (tenter d')annoncer au processeur "tiens compte du branchement qui suit".
                                  Vi mais c'est pas tous les jours qu'on code sur Itanium... D'ailleur si j'ai bien suivit c'est un peu une "horreur" à optimiser ce bousin... Enfin c'est ce qui fait son charme :)

                                  l etablit des statistiques sur les instructions executee dans le passe et il s'en souvient quand il revient dessus?
                                  Bah me semble qu'il y a des architectures qui cherchent à prédire un branchement en fonction des instructions précédentes : c'est pertinent dans une boucle ou par exemple la condition de fin n'est vérifiée qu'une fois, mais je voulais dire que je n'était pas sûr de l'intérêt du truc dans le cas d'une fonction.

                                  Effectivement il peut être intéressant de chercher à indiquer au compilo quelle partie du code risque de brancher pour le "guider" (puisque n'étant pas dans un boucle il aura du mal à prédir), mais comment l'indiquer au processeur ? (à part avec l'Itanium)
                                  Quelle instruction du langage utilises-tu ? Ton code risque d'être spécifique à un compilo et/ou une plateforme...
                                  Enfin pour moi y'a qu'un seul truc dans les langages de programmations qui indiquent ce genre de comportement : les exceptions.

                                  Si tu as plus d'infos sur ce sujet ça m'intéresse.
                                  • [^] # Re: Hmm :/

                                    Posté par  . Évalué à 1.

                                    > Bah me semble qu'il y a des architectures qui cherchent à prédire un branchement en fonction des instructions précédentes

                                    Les processeurs récents contiennent un cache de prédiction, contenant des hash des adresses PC pour les instructions de branchement conditionnel, et pour chaque hash, le résultat précédent du test (sur 1 bit), voire le résultat "moyen" du test (compteur incrémenté / décrementé selon le résultat du test). Ainsi lorsqu'on retrouve l'intruction de branchement on peut choisir la bonne branche. Très utile pour une boucle effectivement : i=0 ; i<100 ; i++ : le résultat du test est le même 99 fois / 100. Notons qu'il peut y avoir une pollution du cache de prédicition si une autre instruction de branchement tombe dans la même case après la fonction de hashage... mais bon c'est mieux que rien.

                                    Je ne pense pas qu'une fonction exécutée "rarement" [exemple de fork() ] n'aille pas en cache. En revanche, elle en sera évincée rapidement, car le code ne sera plus jamais lu.

                                    Il est possible de "dire" au cpu quel est le résultat probable du test : le compilateur connait le processeur pour lequel il compile. Il sait donc, en cas de branchement conditionnel, quelle hypothèse est la plus rapidement exécutée par le cpu. En disant au compilateur qu'une condition est souvent réalisée, il génère le code qui va bien pour que le cpu, sans autre information (cf. branch prediction ci-dessus), prenne la branche la plus probable par défaut (avant d'avoir evalué le résultat du test). Si une condition est rarement réalisée, le compilateur pose test à l 'envers.
                                    [ Note qu'on peut instrumenter le code pour déterminer automatiquement si une condition est souvent ou rarement réalisée, puis recompiler le code à partir de là, pour optimiser le binaire produit ].
                                    Il y a moult publications sur le thème du "branch prediction", c'est important pour les perfos, quoique tu en penses.

                                    Enfin, j'en reviens à mon message précédent auquel tu as répondu je trouve rapidement. Je persiste : si pour tous les tests les bonnes hypothèses sont prises par le cpu, et si le code traitant les branches non exécutées est rejeté "loin" du PC, donc non lu par le chipset de gestion de la ram, il n'y a pas de branchements lointains, et effectivement le cpu peut aller "tout droit", càd avoir toujours un pipeline rempli et des instructions à exécuter.
                                    Je pense qu'on pourrait faire un petit programme de test qui montre les différences de perf en procédant ainsi. Si ça te tente, tiens-nous informé...
                                    • [^] # Re: Hmm :/

                                      Posté par  (site web personnel) . Évalué à 2.

                                      Merci pour cette piqûre de rappel :)

                                      En tout cas on est bien d'accord que dans ce contexte la prédiction de branchement ne permettra pas d'optimiser le fork(), celui-ci n'apparaîssant pas assez souvent (c'est ce que je voulais dire même si je me suis un peu enmêler les pinceaux ^^)

                                      Après je n'avais pas pensé qu'effectivement le fait de mettre le test dans un sens ou dans l'autre changeait le chemin "par défaut", et dans ce cas effectivement le compilo fournit là une information importante suivant le code généré.

                                      Mais comment le dire au compilo ? Même après instrumentation ? Le compilo ne pouvant pas le deviner lui-même (c'est une info dynamique), il faut faire l'optimisation à la main ? (en gros changer nous même la condition ?)

                                      Effectivement dans ces conditions il peut aller tout droit, à vrai dire j'avais mal compris ta phrase je pensais que tu voulais carrement sauter les conditions (mais bon j'aurai dû réfléchir un peu plus)

                                      Après au niveau des perfs tu veux comparer avec quoi ? Un programme qui fait en sorte que le proc se trompe systématiquement de branchement ? Effectivement là le proc risque de devoir systématiquement vider son pipeline, ça va pas être terrible :)

                                      Enfin de toute façon pour en revenir au problème de départ, celà ne change strictement rien, que ce soit if+goto ou if+fonction, les problèmes de prédictions sont les mêmes. Et heuresement on n'a généralement pas besoin d'effectuer l'optimisation que tu suggères, puisque dans la plupart des cas le programme met naturellement le test dans le "bon sens" (sous entend : on effectue un jmp si pas normal), mais il est vrai qu'il faut mieux en être conscient :)
                                      • [^] # Re: Hmm :/

                                        Posté par  (site web personnel) . Évalué à 2.

                                        puisque dans la plupart des cas le programmeUR met naturellement le test dans le "bon sens"
                                      • [^] # Re: Hmm :/

                                        Posté par  . Évalué à 2.

                                        > En tout cas on est bien d'accord que dans ce contexte la prédiction de branchement ne permettra pas d'optimiser le fork(), celui-ci n'apparaîssant pas assez souvent

                                        La prédiction dynamique est dans ce cas inutile : le programmeur sait bien mieux que n'importe qui qu'une allocation mémoire a très peu de chances d'échouer. Ceci explique la mise en forme du code ci-dessus, extrait du noyau.

                                        > Mais comment le dire au compilo ? Même après instrumentation ? Le compilo ne pouvant pas le deviner lui-même (c'est une info dynamique), il faut faire l'optimisation à la main ? (en gros changer nous même la condition ?)

                                        C'est prévu : on instrumente le code, on exécute --> ceci génère un fichier de statistiques, et le compilateur sait relire ce fichier de statistiques lors d'une seconde compilation du même source.
                                        Un exemple d'optimisation sur lequel j'étais tombé une fois :
                                        http://h21007.www2.hp.com/dspp/files/unprotected/linux/Optimization(...)

                                        > dans la plupart des cas le programmeur met naturellement le test dans le "bon sens" (sous entend : on effectue un jmp si pas normal)

                                        Oui, enfin je ne sais pas si tous les cpus suppose la condition vraie avant de l'évaluer, ou s'il la suppose fausse ? Et tout dépend aussi du code généré par le compilateur : il est libre d'inverser le test s'il inverse également les clauses then et else, etc.
                                        • [^] # Re: Hmm :/

                                          Posté par  (site web personnel) . Évalué à 2.

                                          Oué donc ok le compilo est informé lors d'une 2ème passe après profiling. Mais dans ce cas bien précis du kernel linux, fournit tel quel, me semble pas que la procédure standard de compilation utilise cette méthode, si ?
                        • [^] # Re: Hmm :/

                          Posté par  (site web personnel) . Évalué à 3.

                          allez une autre méthode à base de fonction, qui fait que chacun peut gérer son annulation comme il l'entend :

                          bool action1(){
                          if(!do_stuff() || !action2()){
                          cancel_stuff();
                          }
                          }

                          bool action2(){
                          if(!do_stuff() || !action3()){
                          cancel_stuff();
                          }
                          }

                          etc.

                          on utilise ici la pile des appels.
                          avantages : le code est beaucoup plus lisible et maintenable : chaque action contient également le traitement de l'annulation qui n'est pas éparpillé à l'autre bout du code. SAns parler du fait qu'on n'évite de se retrouver avec des méthodes de plusieurs centaines (!!!!) de lignes, qui pour la lisibilité n'est vraimetn pas terrible.

                          En fait il suffit de looker tous les algos qui s'occupe de gérer des transactions atomiques : ils se passent très bien de goto et c'est propre.
                          • [^] # Re: Hmm :/

                            Posté par  (site web personnel) . Évalué à 1.

                            J'aime bien ta solution, conceptuellement, elle est élégante. Juste une remarque cependant, tel que présenté, tu t'appuies implicitement sur l'ordre d'évaluation des expressions en C et notamment sur le fait qu'une expression booléenne ne sera pas évaluée jusqu'au bout si on est sûr qu'elle est fausse. Mais le problème se règle de façon assez évidente avec un problème plus concrêt :


                            bool init1()
                            {
                            bool error = false;

                            error = request_region (...);
                            if (!error)
                            {
                            error = init2 ();
                            if (error)
                            release_region (...);
                            }

                            return error;
                            }

                            bool init2()
                            {
                            bool error = false;

                            error = init_device (...);
                            if (!error)
                            {
                            error = init3 ();
                            if (error)
                            /* nothing to do */;
                            }

                            return error;
                            }


                            Mais pour être franc, cette solution ne me semble adaptée que pour des initialisations lourdes et où l'on souhaite coder le processus d'initialisation en dur.

                            Parce que pour la plupart des initialisations, le nombre d'éléments dans la pile d'initialisation se compte sur les doigts d'une main. Pour ce genre de problème, le goto est une bonne solution puisque :
                            - c'est court, donc plus facile à lire (si tout tient en quelques lignes bien sûr)
                            - c'est une solution connue, elle est donc facilement lisible par tout programmeur ayant un minimum d'expérience
                            - ça ne mets pas en jeu la moindre (méta) donnée (explicite ou même implicite comme dans ta solution), ça ne joue que sur du contrôle

                            Pour les initialisation plus lourdes, il existe une foule d'autres solutions qui feront sans doute usage de données pour décrire la pile d'initialisation. Je n'ai pas d'exemple sous la main, mais à mon avis, c'est plus rare et ta solution sera sans doute en concurrence avec d'autres plus adaptées à des problèmes complexes (par exemple si on ajoutait du parallélisme et des dépendances, mais je m'égare).
                            • [^] # Re: Hmm :/

                              Posté par  (site web personnel) . Évalué à 2.

                              c'est court, donc plus facile à lire
                              Ben entre une méthode qui fait quelques centaines de lignes (comme c'est le cas dans la méthode copy_process prise en exemple plus haut) avec des goto et une dizaine de méthode de quelques lignes, je préfère largement la 2ème solution, pour moi beaucoup plus lisible sur ce que fais chaque bout de code :)

                              c'est une solution connue, elle est donc facilement lisible par tout programmeur ayant un minimum d'expérience
                              Ma méthode est également classique je n'ai rien inventé :)

                              a ne mets pas en jeu la moindre (méta) donnée (explicite ou même implicite comme dans ta solution), ça ne joue que sur du contrôle
                              Elles sont où mes méta-données ? C'est le fait de gérer une pile ? Et ? C'est quoi le problème ? On n'a qu'une dizaine de méthode à tout casser, donc je penses pas que ce soit négatif de l'utiliser ici.
                • [^] # Re: Hmm :/

                  Posté par  (site web personnel) . Évalué à 2.

                  <troll>
                  Ils coderaient en C++, il n'y aurait pas besoin de tout ça, avec des destructeurs propres ...
                  </troll>
              • [^] # Re: Hmm :/

                Posté par  . Évalué à 2.

                L'exemple de la programmation objet [...] alors l'intérêt d'un goto là dedans...

                on est bien d'accord, le goto en C++, c nul. Par contre, en C, pour le traitement des erreurs, c'est très bien. Et là je te rejoins sur le côté exceptionnel des exceptions. Dans un parser qui effectue un certain nombre d'allocations dynamiques dans la même fonction, que doit-on faire pour le traitement des erreurs à chaque test.

                const char *a,*b,*c,*d;
                a = malloc(12);
                if(a==NULL) return;
                b = malloc(12);
                if(b==NULL) { free(a); return; }
                c = malloc(12);
                if(c==NULL) { free(a); free(b); return; }
                etc...

                bref, c'est chiant à mourrir, et qui plus est on duplique le code de libérationde la mémoire. Alors tu peut aussi faire ça dans un do{...}while(0) et faire un break dès que tu as une erreur, mais je ne vois pas la vraie différence avec le goto.
                je préfère de loin

                const char *a=NULL,*b=NULL,*c=NULL,*d=NULL;
                a = malloc(12);
                if(a==NULL) goto fail;
                b = malloc(12);
                if(b==NULL) goto fail;
                c = malloc(12);
                if(c==NULL) goto fail;
                etc...

                /* retour normal */
                return 0;

                fail:
                if(a!=NULL) free(a);
                if(b!=NULL) free(b);
                if(c!=NULL) free(c);
                etc...
                return -1;

                je trouve cela plus clair et plus lisible, je n'ai qu'un seul code de traitement d'erreur, ce qui est suffisant et réduit mon risque d'erreur. C'est de l'émulation d'exceptions, bornée dans le scope de la fonction courante, et moi je dis que c'est bien!

                qui fait de la programmation par aspect

                en "C" ? mouarf! (mettons nous bien d'accord, je parle du goto en "C", rien de plus).

                Ah bon tu remplacent le mot break/continue/return par un goto et tu comprends mieux ?


                for(int i=0; i<347; ++i) {
                do{
                while(!f(i)) {
                if g() break;
                }
                }
                while(h());
                if (g()) continue;
                }

                pour moi, ceci est illisible. le code étant compact, ça reste comphréensible, mais bien dilué, c'est affreux. Reste que c'est vrai, que l'insertion d'un goto ne rendrait pas les choses plus claires, quoi que...

                break/continue/return sont des cas particulier de goto, ont une sémantique et une utilisation précise qui fait que tout le monde les comprend, notamment le compilateur

                Que le compilateur comprenne, ca me parait être la moindre des choses. L'écrivain du code aussi. Le relecteur, je n'en suis pas aussi convaincu que toi. C'est pourquoi, dans la mesure du possible, je n'écrit _jamais_ du code comme cela. Les 'break' et 'continue' sont des nids à ennuis quand on travaille à plusieurs sur le même code.

                alors je suis d'accord, il ne faut pas faire n'importe quoi avec le 'goto'. En dehors du traitement des erreurs, je n'en vois pas l'utilité. Mais ce cas à lui tout seul justifie amplement son utilisation à mes yeux. Alors entendre dire tout le temps qu'un programme qui utilise un goto est un programme mal conçu, c'est des conneries. C'est largement pire d'imbriquer 4 boucles for/do/while avec des break et/ou des continue.


                PS:
                le conseil de linuxfr est :"si vous souhaitez taper du code, n'utilisez pas ce format. [HTML]". => il faut utiliser quoi?
                • [^] # Re: Hmm :/

                  Posté par  (site web personnel) . Évalué à 2.

                  A vrai dire je te rejoins en ajoutant que dans la rubrique "bonnes pratiques" l'utilisation des continue et des break est également déconseillé parcque justement celà nuit à la lisibilité : on préfèrera un invariant de boucle.
                • [^] # Re: Hmm :/

                  Posté par  (site web personnel) . Évalué à 4.

                  C'est dommage que je me souviennes plus de la référence, mais dans le Knuth tome 2, sur la génération de nombre-pseudo aléatoires, ce cher Donald a pondu un joli algo avec des goto partout, que j'ai jamais réussi a déméler entierement... Si quelqu'un retrouve l'algo je suis preneur, c'est dans la génération de nom-pseudo aléatoire par congruence linéaire (les algos avec mélange je crois me souvenir).
                  pour résumé ce joli troll :
                  tu peux faire du spaghetti sans Goto, et faire du propre avec Goto ;-)
            • [^] # Re: Hmm :/

              Posté par  (site web personnel) . Évalué à 3.

              > Alors en C, 'goto' c'est mal. Mais en C++, les exceptions c'est bien. Mouarf :)

              1) Les exceptions, c'est en général d'une fonction a l'autre, les goto, c'est local a une fonction.

              2) Le problème du goto, c'est qu'il est trop général, pas que toutes ses utilisations sont mauvaises. Donc, oui, il y a pleins de trucs qui font un sous ensemble de ce que fait le goto, mais qui sont moins dangereux.
        • [^] # Re: Hmm :/

          Posté par  (site web personnel) . Évalué à 1.

          Imagine une fonction recursive qui sort avec une exception et une autre qui sort avec un goto.
          La premiere te vide la pile en sortant.
          L'autre laisse toutes les traces des appels récursifs. Bingo! Tu viens de pourrir ta pile! tu laisses tourner ce programme en production sur un serveur, et "tiens c'est bizarre! le serveur a encore planté!"

          C'est aussi simple.
          • [^] # Re: Hmm :/

            Posté par  (site web personnel) . Évalué à 4.

            Ne dis pas de conneries... on ne peut pas sortir d'une fonction avec un goto. On parle pas de BASIC là. Il y a longjmp (bien dangereux) pour sauter dans une autre fonction comme un porc. Exemple.
            $ cat test_goto.c
            void fonction_b(void) {
            probleme_en_vue:
                    printf("probleme_en_vue...\n");
            }
             
            void fonction_a(void) {
                    printf("fonction_a\n");
                    goto suite_a;
                    printf("code mort\n");
            suite_a:
                    printf("suite_a\n");
             
                    goto probleme_en_vue;
                    printf("code mort 2\n");
            }
             
            int main(int argc, char *argv[]) {
                    fonction_a();
            }
            $ gcc -o test_goto test_goto.c
            test_goto.c: In function `fonction_a':
            test_goto.c:13: error: label `probleme_en_vue' used but not defined
            $
    • [^] # Re: Hmm :/

      Posté par  . Évalué à 10.

      > Je commence a me demander vraiment pkoi des gens aussi bon en info peuvent se permettre de coder comme des porc en ne respectant meme pas les regles de base que on apprend lors des premiers cours de programmation

      Par ce que tu as appris a coder des applis userland ce qui n'a a peu pres rien a voir avec un noyau ?

      Aller juste un exemple avec un verou, prennons Giant par hasard. Toutes les parties de ton noyau doivent pouvoir faire un lock sur Giant. Tu as la technique où il c'est une variable globale tu fais une jolie macro GIANT_REQUIRED et basta. Ou tu fais une fonction qui va à son tour faire ce que ta macro fait.

      Le gain de la deuxième solution est nul et au mieux ca sera pas plus lent si la fonction est inlinée...

      Je vois pas pourquoi on la virerait ! Enfin y'en a une tripotée comme ca. De même pour savoir si les peripherique doivent etre en mode verbeux ou non lors de l'initialisation. Quel avantage a une fonction "isVerbose" plutot que de tapper directement dans la variable ? Alors peut etre que tes profs t'auraient tappé dessus mais faut aussi reflechir a ce que tu gagnes reellement et ce que tu risques. Et non pas appliquer betement des principes du type "les goto c'est interdit", les variables globale c'est nul etc.

      J'ai pas encore lu le papier pour pouvoir juger du chiffre annoncé dans le journal mais selon ce qui est pris en compte ca pourrait ne pas me choquer plus que ca. Aller chez FreeBSD rien que dans /kern je peux te dire qu'il y a au moins 255 variables globales.
    • [^] # Re: Hmm :/

      Posté par  . Évalué à 3.

      D'aprés ce que je comprends, tu as 3 ans d'iut derriere toi (c'est pas 2 ans normalement l'iut ??), et tu te permets de dire que les gens qui codent le noyaux codent "comme des porcs" ? Je ne sais pas developper, mais ca me parait un peu gros quand même...
      • [^] # Re: Hmm :/

        Posté par  . Évalué à 3.

        Il y a deux porcheries en programmation : (les noms sont de moi :)
        - le look sale (mal indenté, pas documenté, noms de variables pourris...)
        - le code sale (fonctions inutiles, pas de contrôles des valeurs d'entrées => buffers overflow par ex ...)

        Le premier implique facilement le deuxième, mais le deuxième n'implique pas le premier !
      • [^] # Re: Hmm :/

        Posté par  (site web personnel) . Évalué à 4.

        Oui, c'est deux ans le DUT(3 ans pour les branleurs), par contre, tu peux faire une licence en IUT ;) Bon, je l'avoue, j'ai pas fait de licence :D

        Ben, tu veux un exemple, libpam tiens, truc assez important que tu voudrais ne pas etre codé à l'arrache, ben franchement, quand tu vois la tripoter de manipulation de pointeurs nuls dans le code, ca fait un peu peur quand meme. Genre pour tout ceux qui on deja vu "su" faire un segfault(utilisateurs de cooker bonsoir), ben ca vient de libpam et tout ca parce que les monsieur font plusieurs _pam_StrTok() sur un pointeur nul(dont un fatal). Et ce probleme peut amener un systeme linux à etre totalement inutilisable en cas de mauvaise configuration de pam. Et le monsieur qui maintient libpam bosse sur le noyau et j'imagine qu'il est tres compétent. Mais je reste perplexe de voir des erreurs aussi simple a corriger laissés à l'air libre.
        • [^] # Re: Hmm :/

          Posté par  (site web personnel) . Évalué à 6.

          Si elle sont aussi simple à corriger, envoi un patch :)
          • [^] # Re: Hmm :/

            Posté par  (site web personnel) . Évalué à 3.

            J'ai contacté le mainteneur de libpam pour lui signaler le probleme ;) Pour le patch, j'ai jugé ca peut utile ne sachant pas comment le monsieur reglera le probleme: goto, nouveau bloc, ...
    • [^] # Re: Hmm :/

      Posté par  . Évalué à 6.

      En 3 ans d'iut, j'ai jamais utilisé une variable globale dans un seul programme

      même pas errno? mince alors ...
    • [^] # Re: Hmm :/

      Posté par  (site web personnel) . Évalué à 1.

      En 3 ans d'iut, j'ai jamais utilisé une variable globale dans un seul programme et jamais nos prof aurait laissé ca passer.

      Si on apprenait à coder à l'IUT, ça se saurait.

      Lis "The Pragmatic Programmer" et ça te donnera une idée de la raison pour laquelle je dis ça - http://www.pragmaticprogrammer.com/(...)

      Colin, qui s'est fait chier 2 ans en IUT à apprendre des conneries et de belles théories qui marchent mal.
  • # 2.4.20 ?

    Posté par  (site web personnel) . Évalué à 4.

    Juste comme ça en passant, on en est au 2.6.9, alors bon, ça date quand même un peu...
    • [^] # Re: 2.4.20 ?

      Posté par  (site web personnel) . Évalué à 1.

      On en est au 2.4.28 dans la branche 2.4.x ;)
      Et 2.6.10rc3 dans la branche plus recente.

      Mais c'est vrai qu'il aurai été plus interressant que l'on nous parle d'un noyau 2.6...

Suivre le flux des commentaires

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