web-dev-qa-db-fra.com

Pourquoi le compilateur n'optimise-t-il pas une boucle à distance vide sur les éléments d'un ensemble?

Lors du test de mon code, j'ai remarqué une augmentation significative du temps d'exécution lorsque la plage vide -for loop a été supprimé ou non. Normalement, je penserais que le compilateur remarquerait que la boucle for ne sert à rien et serait donc ignoré. Comme drapeaux du compilateur j'utilise -O3 (gcc 5.4). Je l'ai également testé avec un vecteur au lieu d'un ensemble et cela semble fonctionner et donner le même temps d'exécution dans les deux cas. Il semble que l'incrémentation de l'itérateur coûte tout le temps supplémentaire.

Premier cas avec la boucle à distance toujours présente (lente):

#include <iostream>
#include <set>
int main () {
    long result;
    std::set<double> results;
    for (int i = 2; i <= 10000; ++i) {
        results.insert(i);
        for (auto element : results) {
            // no operation
        }
    }
    std::cout << "Result: " << result << "\n";
}

Deuxième cas avec la boucle à distance supprimée (rapide):

#include <iostream>
#include <set>
int main () {
    long result;
    std::set<double> results;
    for (int i = 2; i <= 10000; ++i) {
        results.insert(i);
    }
    std::cout << "Result: " << result << "\n";
}
28
fromhell777

En interne std::set l'itérateur utilise une sorte de chaîne de pointeurs. Cela semble être le problème.

Voici une configuration minimale similaire à votre problème:

struct S
{
    S* next;
};

void f (S* s) {
    while (s)
        s = s->next;
}

Ce n'est pas un problème avec les implémentations de collections complexes ou les frais généraux des itérateurs, mais simplement ce modèle de chaîne de pointeurs que l'optimiseur ne peut pas optimiser.

Je ne connais pas la raison précise pour laquelle les optimiseurs échouent sur ce modèle.

Notez également que cette variante est optimisée loin:

void f (S* s) {
    // Copy paste as many times as you wish the following two lines
    if(s)
        s = s->next;
}

Éditer

Comme suggéré par @hvd, cela pourrait être dû au fait que le compilateur ne peut pas prouver que la boucle n'est pas infinie. Et si nous écrivons la boucle OP comme ceci:

void f(std::set<double>& s)
{
    auto it = s.begin();
    for (size_t i = 0; i < s.size() && it != s.end(); ++i, ++it)
    {
        // Do nothing
    }
}

Le compilateur optimise tout.

23
Guillaume Gris

La plage basée sur la boucle n'est pas aussi triviale qu'elle en a l'air. Il est traduit en boucle basée sur un itérateur en interne dans le compilateur et si l'itérateur est suffisamment complexe, le compilateur peut même ne pas être autorisé par la norme à supprimer ces opérations d'itérateur.

9
Johan

Range-for est du "sucre syntaxique", ce qui signifie qu'il fournit simplement une notation abrégée pour quelque chose qui peut être exprimé de manière plus verbeuse. Par exemple, range-for se transforme en quelque chose comme ça.

for (Type obj : container) ->

auto endpos = container.end();
for ( auto iter=container.begin(); iter != endpos; ++iter)
{
     Type obj(*iter);
     // your code here
}

Maintenant, le problème est que begin/end/* iter/++ iter/(obj =) sont des appels de fonction. Afin de les optimiser, le compilateur doit savoir qu'ils n'ont pas d'effets secondaires (changements à l'état global). L'implémentation est définie par le compilateur ou non et dépend du type de conteneur. Ce que je peux dire cependant, dans la plupart des cas, vous n'avez pas besoin de la fonction (obj =), alors préférez

for (const auto& X: cont) 

ou ...

for (auto& X: cont)

à ...

for (auto X : cont)

Vous trouverez peut-être que cela le simplifie suffisamment pour que les optimisations démarrent.

6
TheAgi

Vous pouvez jouer avec clang rapport d'optimisation. Compilez votre code avec save-optimization-record activé, donc le rapport d'optimisation sera sauvegardé dans main.opt.yaml.

clang++ -std=c++11 main.cpp -O2 -fsave-optimization-record

Clang pense qu'il y a une valeur modifiée dans cette boucle.

- String: value that could not be identified as reduction is used outside the loop

De plus, le compilateur ne peut pas calculer le nombre d'itérations de boucle.

- String: could not determine number of loop iterations

Notez que ce compilateur a correctement intégré begin, end, operator++ et operator=.

6
ivaigult