Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

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 , par Coriolan

0PARTAGES

10  2 
Que pensez-vous de l'utilisation de Goto ?
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 : Sélectionner tout
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 : Sélectionner tout
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 : Sélectionner tout
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 : Sélectionner tout
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 ?

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de Médinoc
Expert éminent sénior https://www.developpez.com
Le 18/11/2016 à 6:34
Plus simple: programmez en GOTO++.
10  0 
Avatar de Florian_PB
Membre averti https://www.developpez.com
Le 18/11/2016 à 10:41
Il m'arrive d'utiliser GoTo avec certains langages où il existe une gestion des erreurs mais aucun Try Catch (VBscript par exemple).
3  0 
Avatar de
https://www.developpez.com
Le 18/11/2016 à 13:38
Citation Envoyé par Florian_PB Voir le message
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.
2  0 
Avatar de Squisqui
En attente de confirmation mail https://www.developpez.com
Le 18/11/2016 à 14:32
Citation Envoyé par CS FS Voir le message
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
2  0 
Avatar de ZwQuery
Membre à l'essai https://www.developpez.com
Le 18/11/2016 à 11:55
goto fail;
goto fail;
1  0 
Avatar de esperanto
Membre éprouvé https://www.developpez.com
Le 18/11/2016 à 12:30
En premier lieu je ne vois pas pourquoi son code serait plus rapide que ceci:

Code : Sélectionner tout
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 : Sélectionner tout
1
2
3
4
if (une-condition) goto fin;
     // quelque chose de très très long
fin:
    // suite
par

Code : Sélectionner tout
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 : Sélectionner tout
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
1  0 
Avatar de CS FS
Membre actif https://www.developpez.com
Le 18/11/2016 à 14:41
Citation Envoyé par Squisqui Voir le message
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).
1  0 
Avatar de ijk-ref
Membre confirmé https://www.developpez.com
Le 18/11/2016 à 15:04
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'
1  0 
Avatar de 23JFK
Membre expérimenté https://www.developpez.com
Le 18/11/2016 à 15:09
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.
1  0 
Avatar de ijk-ref
Membre confirmé https://www.developpez.com
Le 18/11/2016 à 18:54
Citation Envoyé par 23JFK Voir le message
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.
1  0