Articles tagués “for

Laisse faire ton compilateur, il sait ce qu’il fait :)

Ce qui est bien avec les compilateurs modernes, c’est qu’ils sont très bons pour optimiser le code à notre place. Un bon code est un code efficace mais simple, que le compilateur comprendra et analysera facilement. Le développeur pourra décider d’écrire un code « plus optimisé » si et seulement si le compilateur n’a pas réussi à produire quelque chose de satisfaisant. Mais il devrait quand même d’abord essayer de clarifier son code 😉

J’en ai encore fait l’expérience ces jours-ci en me demandant si un ranged-loop for (apparu en C++11) sur un tableau avait un overhead par rapport à un parcours classique avec un index de 0 à taille-1. Je suis donc aller sur Compiler Explorer et j’ai écrit ce petit code :

void f(int v);

constexpr int SIZE = 128;
int array[SIZE];

void g() {
    for(auto& e : array) {
        f(e);
    }
}

void h() {
    for(int i = 0; i < SIZE; ++i) {
        auto& e = array[i];
        f(e);
    }
}

Je l'ai ensuite compilé avec GCC 7.2.1 pour ARM, puisque c'est la toolchain que j’utilise sur mon projet actuel. Avec l’optimisation à O2 ou O3, les codes assembleurs générés sont les mêmes. En revanche, une des versions est un poil meilleure en O1…. et ce n’est pas la version classique mais la version mode du ranged-loop for. Sans doute parce qu’il n’y a aucune ambiguïté sur le code : il s’agit bien de parcourir le tableau élément par élément. La différence est minime toutefois 😉

Voici ce que ça donne (les couleurs montrent la correspondance code source / code assembleur) :

 

Conclusion : laissez faire votre compilateur, il sait ce qu’il fait…. et sans doute bien mieux que vous !


Boucle infinie en C : for ou while ?

On s’est déjà posé cette question, on a déjà vu ou entendu des gens la poser : faut-il faire une boucle infinie avec while (1) ou avec for( ; ; ) en C ?

La logique veut que le résultat soit le même. J’ai donc fait le test avec le compilateur MinGW sous Windows 7 64 bits. J’ai pour cela écrit deux fonctions placées dans le fichier boucles.c :

void withFor(void)
{
    for(;;)
        ;
}

void withWhile(void)
{
    while(1)
        ;
}

J’ai ensuite utilisé objdump pour déassembler le code :

PS D:\Users\pgradot\Documents\C\out\Debug> objdump.exe -d .\boucles.o
.\boucles.o:     file format pe-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   eb fe                   jmp    3 

00000005 :
   5:   55                      push   %ebp
   6:   89 e5                   mov    %esp,%ebp
   8:   eb fe                   jmp    8 
   a:   90                      nop
   b:   90                      nop

Les 2 NOP à la fin m’ont étonné. J’ai inversé les positions des fonctions dans le fichier et j’ai alors constaté que les 2 NOP étaient toujours à la fin du fichier déassemblé. Pour m’assurer qu’ils ne faisaient effectivement pas partie des fonctions (vu les JMP, c’était quasiment certain), j’ai rajouté une fonction à la fin du fichier :

void withWhile(void)
{
    while(1)
        ;
}

void withFor(void)
{
    for(;;)
        ;
}

void nop()
{

}

Voici le résultat en assembleur :

PS D:\Users\pgradot\Documents\C\out\Debug> objdump.exe -d .\boucles.o

.\boucles.o:     file format pe-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   eb fe                   jmp    3 

00000005 :
   5:   55                      push   %ebp
   6:   89 e5                   mov    %esp,%ebp
   8:   eb fe                   jmp    8 

0000000a :
   a:   55                      push   %ebp
   b:   89 e5                   mov    %esp,%ebp
   d:   5d                      pop    %ebp
   e:   c3                      ret
   f:   90                      nop

Il n’y a donc aucune différence de performance entre les deux boucles, en tout cas avec ce compilateur et cette cible. J’ai des résultats similaires avec llvm/gcc et otool sous Mac OS X. Il en y a qui disent que for( ; ; ) donnerait de meilleures performances que while (1) car il y a une évaluation de condition dans la deuxième écriture. A l’évidence, mon compilateur ne se laisse pas avoir. Ce n’était peut-être pas le cas avec des compilateurs anciens mais je pense qu’on peut dire que cela relève maintenant des mythes du passé.

Il peut enfin rester des considérations sémantiques : laquelle des deux écritures représentent mieux l’idée de boucle infinie ? Je pense que cette discussion de stackoverflow les résume bien. En particulier, j’aime beaucoup l’écriture suivante qui y est proposée et donc la sémantique est parfaite ^^ :

#define EVER ;;

void forever(void)
{
        for(EVER)
                ;
}

On peut regarder le code en sortie du pré-processeur :

gcc -E -P boucles.c 

void forever(void)
{
 for(;;)
  ;
}

Et enfin l’assembleur généré :

otool -tv boucles.o 
boucles.o:
(__TEXT,__text) section
_forever:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp,%rbp
0000000000000004	jmp	0x00000004

Avis personnel : je met toujours while (1) (ou while (true) en Java). C’est pour moi l’écriture la plus claire à lire et surtout la plus facile à dire.
« Hey machin, j’ai fait une boucle for(;;) !
– Gné ?!
– Oui, un while un quoi !
– Ahhhhh ! »


Concevoir un site comme celui-ci avec WordPress.com
Commencer