Cette article est une analyse de performance des différentes forme de boucles for que on peut rencontrer dans un programme C++.

Pour la mesure on utilise une petite class record qui se chargera d’enregistrer le temps avant et après l’exécution des boucles et d’en faire la différence pour avoir le temps total qui prendra chaque boucle for.

class record
{
public:
    record()
    {
        start = clock();
    }

    void echo(string label)
    {
        curr = clock();
        cout << label << " : " << (curr - start) / double(CLOCKS_PER_SEC) << "s" << endl;
        start = curr;
    }

private:
     time_t start, curr;
};

Pour mettre en situation de gros volume de données on vas générer un tableau d’int de 512^3 éléments soit 134.217.728 éléments et le remplir avec des valeurs aléatoire a l’aide de la fonction standard rand().

srand(time(0));

vector array;
array.resize(pow(512, 3));
generate(array.begin(), array.end(), rand);

cout << "array.size() = " << array.size() << endl;

Ensuit dans les boucle on passera chaque valeur a une fonction cube() qui fera office de traitement.

void cube(int& v)
{
    v *= v;
}

Analyse…

Itération normal

On commande donc par une iteration normale de tout ce qu’il ya de plus classique pour la prendre comme base de comparaison.

for(unsigned i=0; i

Résultat ca prend 1 seconde et demi

Normal iteration : 1.5s

Itération avec taille mise en cache

Cette fois si on va enregistrer la taille du tabealux dans une variable pour eviter un appelle de fonction inutile.

unsigned size = array.size();

for(unsigned i=0; i

On constat qu’on a gagner environ 0.2 seconde on commence donc a améliorer.

Cached size : 1.344s

Utilisation des iterator

On passe maintenant a une autre type d’itération, avec les std::iterator, on utilise pour cela les méthode begin() et end() de la class vector.

for(vector::iterator it = array.begin(); it != array.end(); ++it)
    cube(*it);

La on régresse, en effet on a passer de 1 a 3 secondes, on se rend compte donc que les std::iterator ne sont pas optimiser pour une itération normale qu’on aurait pu faire avec une méthode classique.

Iterator : 3.75s

Utilisation des iterator (mise en cache)

Maintenant on teste avec la mise en cache de l’itérateur de fin.

vector::iterator end = array.end();

for(vector::iterator it = array.begin(); it != end; ++it)
    cube(*it);

Résultat. une baisse d’à peu près 1.5 secondes.

Cached end iterator : 2.359s

La fonction for_each

Cette fois si, on va utiliser la fonction standard for_each.

for_each(array.begin(), array.end(), cube);

C’est un peu plus long que la méthode précédente.

for_each : 2.422s

Compilation…

Pour les besoin de cette petite étude j’ai compiler le programme de deux façon une normalement, et une avec l’option du compilateur -o2 qui va normalement optimiser les performance de notre programme.

g++ main.cpp -o foreach.exe
g++ -O2 main.cpp -o foreach-o2.exe

Résultat avec l’option -o2

Normal iteration : 0.328s
Cached size : 0.312s
Iterator : 0.328s
Cached end iterator : 0.328s
for_each : 0.313s

On se rend compte avec l’option -o2 que le compilateur a fait un travaille d’optimisation sur nos boucle et qu’il ya peut de différence entre une méthode et une autre.

On constat alors que quelle que soit la méthode utiliser pour la boucle en mode -o2 le compilateur sait reconnaître et optimiser les zone qui peuvent l’être.