Developpez.com

Le Club des Développeurs et IT Pro

Trolldi : GOTO est formidable, elle permet d'accélérer 2 à 3 fois la vitesse d'exécution du code,

Que pensez-vous de son utilisation ?

Le 2016-11-17 17:12:04, par Coriolan, Expert éminent sénior
Goto est une instruction héritée des premiers langages informatiques, époque où certaines instructions très connues actuellement n’existaient pas, comme les boucles et les structures de contrôle. Néanmoins, même à l’âge moderne de l’informatique et après avoir été décriée par plusieurs informaticiens de renom, cette instruction est encore utilisée, et cela même au sein des géants de l’informatique comme Apple ; à titre d’exemple la recherche du mot-clé goto sur un portail tel que GitHub débouche sur des millions de résultats.


« Je peux soit restructurer le code ou utiliser une petite GOTO à la place. Tant pis, ça ne peut pas être si terrible que ça, goto main_sub3; »

Depuis les 1970s, les programmeurs modernes ont commencé à rejeter cette instruction, sous le motif qu’elle rend les programmes plus difficiles à comprendre et à maintenir (on parle dans ce cas de programmation spaghetti). Depuis lors, on a commencé à recourir à des structures comme les conditionnelles (if .. then .. else ..) ou les boucles (for, while, etc.) qui font partie intégrante de tous les langages de programmation impératifs modernes.

Edsger Dijkstra et Niklaus Wirth ont défendu l'idée selon laquelle l'instruction goto ne peut que mener à du code illisible. D'autres, comme Linus Torvalds ou Robert Love, ont fait remarquer que même si elle incite à produire du code spaghetti, l'instruction goto peut être appréciable et rendre au contraire le code plus lisible, lorsqu'elle est employée à bon escient.

Alors dans quel cas cette instruction est-elle encore utilisée ? Parmi les cas de figure évoqués existent la sortie d’une boucle imbriquée ce qui épargne le recours à plusieurs break, l’amélioration de la lisibilité du code et le traitement des erreurs ou encore l’optimisation manuelle du code pour améliorer les performances.

Dans les deux premiers cas, toute utilisation parcimonieuse semble correcte, mais pas dans le dernier cas selon Jeff Law et Jason Merril, tous les deux ingénieurs chez Red Hat et membres du comité du compilateur GCC. En effet, ils expliquent que l’optimisation manuelle du code n’est plus à l’ordre du jour, car les compilateurs modernes sont suffisamment développés pour se charger gracieusement de cette tâche, en transformant le code en entrée en une série de blocs de base et en se reposant sur l’utilisation d’un graphe de flot de contrôle (GFC). Résultat des courses aucune distinction entre un code bien structuré et un code en spaghetti (qui résulte d’une addiction au goto).

Maintenant dans le monde réel, RASTER montre comment l’instruction GOTO peut être utile quelques fois.

Code c :
1
2
3
4
5
6
7
8
if (shared) lock(); 
if (data == INVALID) { 
  log_error("blah %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, data); 
  return NULL; 
} 
// smallish body of code hunting through some nested tables using data 
if (shared) unlock(); 
return realdata;


lock() et unlock() sont déclarées static inline pour faciliter la vérification d’erreurs et le logging et aussi pour faciliter la vie au développeur. Alors if(shared) lock(); devient :

Code c :
1
2
3
4
5
6
7
if (shared) { 
  if (lock == VALID) { 
    if (!do_lock(lock)) { 
      log_error("lock fail %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, lock); 
    } 
  } 
}


La même chose pour unlock().

Maintenant ce code devrait verrouiller une ressource partagée, aller chercher quelques données et possiblement les déverrouiller et les retourner tout en manipulant les erreurs au passage. Raster s’est rendu compte que ce segment de code consommait entre 6 et 7 % du CPU, ce qui fait un peu beaucoup.

Alors il a passé un peu de temps à réorganiser tout ça :

Code c :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (shared) { 
  lock(); 
  if (data == INVALID) { 
    log_error("blah %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, data); 
    return NULL; 
  } 
  // smallish body of code hunting through some nested tables using data 
  unlock(); 
} else { 
  if (data == INVALID) { 
    log_error("blah %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, data); 
    return NULL; 
  } 
  // smallish body of code hunting through some nested tables using data 
} 
return realdata;


Malgré cela, l'usage du CPU utilisé est resté le même (6-7 %). Raster s’est rendu compte que le locking est coûteux en ressources. Alors il a décidé de recourir à l’instruction GOTO pour gérer les exceptions qui sont rares, en transférant ces cas à la fin de la fonction, pour laisser place aux autres cas plus communs. Cette manoeuvre lui a permis de réduire les pertes de cache.

Code c :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (!shared) { 
  if (data != INITIALIZED) goto doinit_shared; 
  doinit_shared_back: 
  if (data == INVALID) goto err_invalid; 
  // smallish body of code hunting through some nested tables using data 
} else { 
  lock(); 
  if (data != INITIALIZED) goto doinit_shared; 
  doinit_shared_back: 
  if (data == INVALID) goto err_invalid; 
  // smallish body of code hunting through some nested tables using data 
  unlock(); 
  return realdata; 
} 
return realdata; 
doinit_shared: 
  // a few lines of initting data here 
  goto doinit_shared_back; 
doinit: 
  // a few lines of initting data here 
  goto doinit_back; 
err_invalid: 
  log_error("blah %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, data); 
  return NULL;


Et voilà, l’usage du CPU est tombé à 2,5 % soit 2 à 3 fois la vitesse initiale.

En gros, Raster pense que rien n’est complètement méchant, bien sûr il ne faut pas utiliser GOTO pour remplacer les boucles et les structures de contrôle. Mais pour déplacer le code loin du hot path et manipuler les exceptions, Raster pense que c’est admissible et ça permet de rendre le code plus lisible et fournit surtout une performance accrue qui passe par dessus toute laideur perçue dans le code.

Source : Rasterman

Et vous ?

Qu'en pensez-vous ?
  Discussion forum
49 commentaires
  • Médinoc
    Expert éminent sénior
    Plus simple: programmez en GOTO++.
  • Florian_PB
    Membre averti
    Il m'arrive d'utiliser GoTo avec certains langages où il existe une gestion des erreurs mais aucun Try Catch (VBscript par exemple).
  • Envoyé par Florian_PB
    Il m'arrive d'utiliser GoTo avec certains langages où il existe une gestion des erreurs mais aucun Try Catch (VBscript par exemple).
    Il y a aussi quelques langages de calculatrices et bien sur le langage assembleur.
  • Squisqui
    En attente de confirmation mail
    Envoyé par CS FS
    sur le coup, j’ai même trouvé la solution élégante. Ça donnait un truc du style :
    Je crois que l'exemple n'illustre pas les bienfaits de ton lourd secret
  • ZwQuery
    Membre à l'essai
    goto fail;
    goto fail;
  • esperanto
    Membre émérite
    En premier lieu je ne vois pas pourquoi son code serait plus rapide que ceci:

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if (!shared) {
      if (data != INITIALIZED) { // a few lines of initting data here (1) }
      if (data == INVALID) { log_error("blah %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, data); return NULL; }
      // smallish body of code hunting through some nested tables using data
    } else {
      lock();
      if (data != INITIALIZED) { // a few lines of initting data here (2) }
       if (data == INVALID) { log_error("blah %s:%i %s() -> %p\n", __FILE__, __LINE__ __FUNC__, data); return NULL; }
      // smallish body of code hunting through some nested tables using data
      unlock();
      return realdata;
    }
    return realdata;
    à la limite, si la partie "// a few lines..." est suffisamment longue, il faut la remplacer par une procédure inline statique pour éviter d'avoir à la répéter. D'autant plus que l'auteur dit vouloir faire ça pour "déplacer le code loin du hot path", ce qu'une procédure inline permet justement de faire.

    En plus de ça le code initial a deux étiquettes nommées doinit_shared_back: donc j'ai même du mal à comprendre comment ça peut fonctionner.

    Maintenant, concernant l'usage du GOTO en général, même si j'ai toujours réussi à l'éviter, je pense que ce qui nuit à la lisibilité du code, c'est moins l'usage d'un goto que d'une procédure à rallonge. Et comme la programmation spaghetti se fait sans procédure, le code est forcément à rallonge.

    Le plus souvent quand on apprend aux programmeurs à se passer du goto, c'est en remplaçant

    Code :
    1
    2
    3
    4
    if (une-condition) goto fin;
         // quelque chose de très très long
    fin:
        // suite
    par

    Code :
    1
    2
    3
    4
    if (! une-condition) {
         // quelque chose de très très long
    }
        // suite
    alors que ce qui rendrait vraiment le code plus lisible, c'est de déplacer "quelque chose de très long" par une procédure, fut-elle inline si les performances sont tellement cruciales. Même si elle n'est appelée qu'une seule fois dans le code, le seul fait de lui choisir un nom et des paramètres explicites est déjà une documentation du code en soi.

    bien sûr il ne faut pas utiliser GOTO pour remplacer les boucles et les structures de contrôle.
    Moi j'ai vu pire : un collègue qui utilise des boucles pour remplacer le goto. C'est à dire que pour la dernière instruction que j'ai écrite, lui choisira ceci :

    Code :
    1
    2
    3
    4
    5
    boucle: do {
         if (une-condition) break boucle;
         // quelque chose de très très long
    } while (0);
        // suite
    Comme ça il satisfait ses profs qui lui ont dit "pas de goto" (mais visiblement sans expliquer pourquoi).
    Évidemment ça fonctionne puisque la boucle est exécutée une fois maximum à cause du while(0). Mais pour la lisibilité, on repassera
  • CS FS
    Membre averti
    Envoyé par Squisqui
    Je crois que l'exemple n'illustre pas les bienfaits de ton lourd secret
    Pour ma défense j'étais jeune et insouciant (des lignes de code ont coulé depuis... en outre, je ne me souviens plus du cas de figure exact qui m'avait poussé à réaliser cet acte hérétique).
  • ijk-ref
    Membre éclairé
    Pour les boucles imbriquées, je me demande toujours pourquoi n'existe-il pas simplement un "break n" pour quitter directement 'n' boucles. POURQUOI §§§

    Ca devient vite moche un 'switch' dans une boucle. Afin AMHA l'instruction 'switch' est inadaptée pour des usages modernes comme en c#. Je préfèrerais une gestion plus classique des 'cas' comme un "elsecase(cond){}" plutôt qu'un 'label' et son 'break'
  • 23JFK
    Membre expert
    goto est l'équivalent assembleur de l'instruction JMP qui est l'une des commandes les plus utilisées dans un code exécutable. Par ailleurs, l'optimisation d'un code source par le compilateur repose largement sur l'ajout de JMP dans le code exécutable alors ce ne sont pas quelques goto pas trop mal placés dans le code source qui vont altérer la performance finale du code exécutable optimisé. Pour s'en convaincre, il suffit de désassembler un code exécutable issu d'une compilation optimisée à partir d'un fichier source qui n'utilise aucun goto pour constater que cela génère tout le temps du code spaghetti.
  • ijk-ref
    Membre éclairé
    Envoyé par 23JFK
    goto est l'équivalent assembleur de l'instruction JMP qui est l'une des commandes les plus utilisées dans un code exécutable. Par ailleurs, l'optimisation d'un code source par le compilateur repose largement sur l'ajout de JMP dans le code exécutable alors ce ne sont pas quelques goto pas trop mal placés dans le code source qui vont altérer la performance finale du code exécutable optimisé. Pour s'en convaincre, il suffit de désassembler un code exécutable issu d'une compilation optimisée à partir d'un fichier source qui n'utilise aucun goto pour constater que cela génère tout le temps du code spaghetti.
    Je ne comprends pas l'intérêt et la pertinence de ton message.

    Super les compilateurs transforment du codes haut niveau en bas niveau avec plein de gotos dedans. Donc faut en conclure que c'est tout bon pour utiliser des gotos en haut niveau !

    Les compilateurs transforment aussi les variables nommées en adresse brutes... je vais en faire autant cela ne doit pas être si mal.

    Et puis sortir des tautologies comme "quelques gotos pas trop mal placés" c'est bien parce que... bah ils sont pas trop mal placées alors évidemment qu'ils font de bonnes choses

    Alors je te répondrais que le danger c'est les gotos mal placés... je ne risque pas plus de me tromper.