Le drapeau de déroulement de boucle GCC est-il vraiment efficace?

En C, j’ai une tâche où je dois faire des multiplications, des inversions, des transpositions, des additions, etc., etc.

J’ai trouvé le drapeau gcc -funroll-all-loops . Si je comprends bien, cela déroulera toutes les boucles automatiquement sans aucun effort du programmeur.

Mes questions:

a) Est-ce que gcc inclut ce type d’optimisation avec les différents drapeaux d’optimisation tels que -O1 , -O2 etc.?

b) Dois-je utiliser un pragma dans mon code pour tirer parti du déroulement de boucle ou des boucles sont-elles identifiées automatiquement?

c) Pourquoi cette option n’est-elle pas activée par défaut si le déroulement augmente les performances?

d) Quels sont les indicateurs d’optimisation gcc recommandés pour comstackr le programme de la meilleure façon possible? (Je dois exécuter ce programme optimisé pour une seule famille de processeurs, identique à celui de la machine sur laquelle je comstack le code. En fait, j’utilise les indicateurs march=native et -O2 )

MODIFIER

Il semble qu’il y ait des controverses sur l’utilisation de dérouler qui, dans certains cas, peuvent ralentir les performances. Dans mes situations, il existe différentes méthodes qui effectuent simplement des opérations mathématiques dans 2 cycles nesteds pour des éléments de masortingce itérés effectués pour une quantité énorme d’éléments. Dans ce scénario, comment se dérouler pourrait ralentir ou augmenter les performances?

    Pourquoi dérouler des boucles?

    Instructions de pipeline de processeurs modernes. Ils aiment savoir ce qui va se passer et font toutes sortes d’optimisations sophistiquées en fonction d’hypothèses sur l’ordre dans lequel les instructions doivent être exécutées.

    Au bout d’une boucle, il y a deux possibilités! Soit vous remontez au sumt, soit vous continuez. Le processeur fait une supposition éclairée sur ce qui va se passer. Si tout se passe bien, tout va bien. Sinon, il doit vider le pipeline et restr bloqué pendant qu’il se prépare à prendre l’autre twig.

    Comme vous pouvez l’imaginer, dérouler une boucle élimine les twigs et les risques de blocage, en particulier dans les cas où les chances sont contre-devinettes.

    Imaginez une boucle de code qui s’exécute 3 fois, puis continue. Si vous supposez (comme le processeur le ferait probablement) qu’à la fin, vous répétez la boucle. 2/3 du temps, vous aurez raison! 1/3 du temps cependant, vous allez caler.

    D’autre part, imaginez la même situation, mais le code boucle 3000 fois. Ici, il n’y a probablement qu’un gain de 1/3000 du temps de déroulement.

    Pourquoi ne pas dérouler des boucles?

    Une partie de la fantaisie du processeur mentionnée ci-dessus consiste à charger les instructions de l’exécutable en mémoire dans le cache d’instructions intégré du processeur (abrégé en I-cache). Ceci contient un nombre limité d’instructions auxquelles on peut accéder rapidement, mais peut s’arrêter lorsque de nouvelles instructions doivent être chargées à partir de la mémoire.

    Revenons aux exemples précédents. Supposons qu’une petite quantité de code dans la boucle occupe n octets d’I-cache. Si nous déroulons la boucle, elle occupe maintenant n * 3 octets. Un peu plus, mais cela ira probablement dans une seule ligne de cache afin que votre cache fonctionne de manière optimale et ne nécessite pas de blocage de la lecture de la mémoire principale.

    La boucle 3000, cependant, se déroule pour utiliser un énorme n * 3000 octets d’I-cache. Cela va nécessiter plusieurs lectures à partir de la mémoire et probablement pousser d’autres choses utiles ailleurs dans le programme hors du cache I-cache.

    Alors qu’est-ce que je fais?

    Comme vous pouvez le constater, le déroulement offre plus d’avantages pour les boucles plus courtes, mais finit par entamer les performances si vous avez l’intention de faire une boucle un grand nombre de fois.

    Habituellement, un compilateur intelligent doit deviner les boucles à dérouler, mais vous pouvez le forcer si vous êtes certain de mieux le connaître. Comment faites-vous pour mieux connaître? Le seul moyen est d’essayer les deux et de comparer les horaires!

    L’optimisation prématurée est la racine de tout mal – Donald Knuth

    Profil d’abord, optimisez plus tard.

    Le déroulage de boucle ne fonctionne pas si le compilateur ne peut pas prédire le nombre exact d’itérations de la boucle au moment de la compilation (ou au moins prédire une limite supérieure, puis ignore autant d’itérations que nécessaire). Cela signifie que si la taille de votre masortingce est variable, le drapeau n’aura aucun effet.

    Maintenant, pour répondre à vos questions:

    a) Est-ce que gcc inclut ce type d’optimisation avec les différents drapeaux d’optimisation tels que -O1, -O2, etc.?

    Non, vous devez le définir explicitement, car cela peut rendre le code plus rapide ou pas et rendre l’exécutable plus volumineux.

    b) Dois-je utiliser des pragmas dans mon code pour tirer parti du déroulement de boucle ou des boucles sont-elles identifiées automatiquement?

    Pas de pragmas. Avec -funroll-loops le compilateur détermine de manière heuristique les boucles à dérouler. Si vous voulez forcer le déroulement, vous pouvez utiliser -funroll-all-loops , mais cela -funroll-all-loops généralement l’exécution du code.

    c) Pourquoi cette option n’est-elle pas activée par défaut si le déroulement augmente les performances?

    Cela n’augmente pas toujours les performances! En outre, tout ne concerne pas la performance. Certaines personnes se soucient vraiment d’avoir de petits exécutables car elles ont peu de mémoire (voir: systèmes intégrés)

    d) Quels sont les indicateurs d’optimisation gcc recommandés pour comstackr le programme de la meilleure façon possible? (Je dois exécuter ce programme optimisé pour une seule famille de processeurs, identique à celui de la machine sur laquelle je comstack le code. En fait, j’utilise les indicateurs march = native et -O2)

    Il n’y a pas de solution miracle. Vous devrez réfléchir, tester et voir. Il existe en fait un théorème selon lequel aucun compilateur parfait ne peut exister.

    Avez-vous profilé votre programme? Le profilage est une compétence très utile pour ces choses.

    Source (principalement): https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html

    Vous obtenez une formation théorique sur le problème et cela laisse suffisamment d’espace pour deviner ce que vous obtenez réellement. On dit que l’option n’augmente pas toujours les performances, car elle dépend de nombreux facteurs, par exemple l’implémentation de la boucle, sa charge / son corps, etc.

    Chaque code est différent et si vous êtes intéressé par la solution la plus performante, il est judicieux d’exécuter les deux variantes, de mesurer leurs temps d’exécution et de les comparer.

    Regardez cette approche dans la réponse ci-dessous pour avoir une idée de la mesure du temps. En deux mots, vous venez d’envelopper votre code dans le cycle, ce qui fera courir votre programme en plusieurs secondes. Comme vous optimisez vous-même les boucles, il est judicieux d’écrire un script shell, qui exécute votre application plusieurs fois.