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.
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 :
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 :
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.
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 ?
« 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 ?
-
MédinocExpert éminent séniorPlus simple: programmez en GOTO++.le 18/11/2016 à 6:34
-
Florian_PBMembre avertiIl m'arrive d'utiliser GoTo avec certains langages où il existe une gestion des erreurs mais aucun Try Catch (VBscript par exemple).le 18/11/2016 à 10:41
-
Il y a aussi quelques langages de calculatrices et bien sur le langage assembleur.le 18/11/2016 à 13:38
-
SquisquiEn attente de confirmation maille 18/11/2016 à 14:32
-
ZwQueryMembre à l'essaigoto fail;
goto fail;
le 18/11/2016 à 11:55 -
esperantoMembre émériteEn 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
13if (!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;
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çantCode : 1
2
3
4if (une-condition) goto fin; // quelque chose de très très long fin: // suite
Code : 1
2
3
4if (! une-condition) { // quelque chose de très très long } // suite
bien sûr il ne faut pas utiliser GOTO pour remplacer les boucles et les structures de contrôle.Code : 1
2
3
4
5boucle: do { if (une-condition) break boucle; // quelque chose de très très long } while (0); // suite
Évidemment ça fonctionne puisque la boucle est exécutée une fois maximum à cause du while(0). Mais pour la lisibilité, on repasserale 18/11/2016 à 12:30 -
CS FSMembre avertiPour 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).le 18/11/2016 à 14:41
-
ijk-refMembre é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'le 18/11/2016 à 15:04 -
23JFKMembre expertgoto 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.le 18/11/2016 à 15:09
-
ijk-refMembre éclairé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.le 18/11/2016 à 18:54