32 votes

Pourquoi clang produit-il un code beaucoup plus rapide que gcc pour cette fonction simple impliquant l'exponentiation?

Le code suivant compilé avec clang est de près de 60 fois plus rapide que celui compilé avecgcc avec les mêmes options de compilation (soit -O2 ou -O3):

#include <iostream>
#include <math.h> 
#include <chrono>
#include <limits>

long double func(int num)
{
    long double i=0;
    long double k=0.7;

    for(int t=1; t<num; t++){
      for(int n=1; n<16; n++){
        i += pow(k,n);
      }
    }
    return i;
}


int main()
{
   volatile auto num = 3000000; // avoid constant folding

   std::chrono::time_point<std::chrono::system_clock> start, end;
   start = std::chrono::system_clock::now();

   auto i = func(num);

   end = std::chrono::system_clock::now();
   std::chrono::duration<double> elapsed = end-start;
   std::cout.precision(std::numeric_limits<long double>::max_digits10);
   std::cout << "Result " << i << std::endl;
   std::cout << "Elapsed time is " << elapsed.count() << std::endl;

   return 0;
}

J'ai testé avec trois gcc versions 4.8.4/4.9.2/5.2.1 et deux clang versions 3.5.1/3.6.1 et voici les horaires sur ma machine (pour gcc 5.2.1 et clang 3.6.1):

Timing -O3:

gcc:    2.41888s
clang:  0.0396217s 

Timing -O2:

gcc:    2.41024s
clang:  0.0395114s 

Timing -O1:

gcc:    2.41766s
clang:  2.43113s

Il semble donc que, gcc ne permet pas d'optimiser cette fonction à tous, même à des niveaux d'optimisation. L'assemblée de la sortie de l' clang est presque autour de 100 lignes de plus de gcc et je ne pense pas qu'il est nécessaire de le poster ici, tout ce que je peux dire, c'est que, en gcc assemblée de sortie, il est un appel à l' pow qui n'apparaît pas dans clang de l'assemblée, sans doute parce qu' clang il optimise pour un tas de intrinsèques des appels.

Puisque les résultats sont identiques (i.e. i = 6966764.74717416727754), la question est:

  1. Pourquoi peut - gcc pas d'optimiser cette fonction lors de l' clang peut?
  2. Modifier la valeur de k de 1.0 et gcc devient aussi rapide, est-il une arithmétique à virgule flottante question qu' gcc ne peut de by-pass?

J'ai essayé d' static_casting et activé les mises en garde pour voir si il n'y avait aucun problème avec les conversions implicites, mais pas vraiment.

Mise à jour: Pour être complet voici les résultats pour -Ofast

gcc:    0.00262204s
clang:  0.0013267s

Le point est que, gcc ne permet pas d'optimiser le code en O2/O3 niveaux, et j'ai peut-être incorrect, mais pas de corps utilise -Ofast pour la presse/le niveau de production de code.

33voto

Shafik Yaghmour Points 42198

À partir de ce godbolt session clang est capable d'effectuer toutes les de la pow calculs au moment de la compilation. Il sait au moment de la compilation de ce que les valeurs de k et n sont et il vient constant des plis le calcul:

.LCPI0_0:
    .quad   4604480259023595110     # double 0.69999999999999996
.LCPI0_1:
    .quad   4602498675187552091     # double 0.48999999999999994
.LCPI0_2:
    .quad   4599850558606658239     # double 0.34299999999999992
.LCPI0_3:
    .quad   4597818534454788671     # double 0.24009999999999995
.LCPI0_4:
    .quad   4595223380205512696     # double 0.16806999999999994
.LCPI0_5:
    .quad   4593141924544133109     # double 0.11764899999999996
.LCPI0_6:
    .quad   4590598673379842654     # double 0.082354299999999963
.LCPI0_7:
    .quad   4588468774839143248     # double 0.057648009999999972
.LCPI0_8:
    .quad   4585976388698138603     # double 0.040353606999999979
.LCPI0_9:
    .quad   4583799016135705775     # double 0.028247524899999984
.LCPI0_10:
    .quad   4581356477717521223     # double 0.019773267429999988
.LCPI0_11:
    .quad   4579132580613789641     # double 0.01384128720099999
.LCPI0_12:
    .quad   4576738892963968780     # double 0.0096889010406999918
.LCPI0_13:
    .quad   4574469401809764420     # double 0.0067822307284899942
.LCPI0_14:
    .quad   4572123587912939977     # double 0.0047475615099429958

et il se déroule à l'intérieur de la boucle:

.LBB0_2:                                # %.preheader
    faddl   .LCPI0_0(%rip)
    faddl   .LCPI0_1(%rip)
    faddl   .LCPI0_2(%rip)
    faddl   .LCPI0_3(%rip)
    faddl   .LCPI0_4(%rip)
    faddl   .LCPI0_5(%rip)
    faddl   .LCPI0_6(%rip)
    faddl   .LCPI0_7(%rip)
    faddl   .LCPI0_8(%rip)
    faddl   .LCPI0_9(%rip)
    faddl   .LCPI0_10(%rip)
    faddl   .LCPI0_11(%rip)
    faddl   .LCPI0_12(%rip)
    faddl   .LCPI0_13(%rip)
    faddl   .LCPI0_14(%rip)

Remarque, que c'est à l'aide d'une fonction builtin(gcc documents de leurs, icipour calculer les pow au moment de la compilation, et si nous utilisons -fno-builtin il n'assure plus cette optimisation.

Si vous modifiez k de 1.0 puis de gcc est capable d'effectuer la même optimisation:

.L3:
    fadd    %st, %st(1) #,
    addl    $1, %eax    #, t
    cmpl    %eax, %edi  # t, num
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    fadd    %st, %st(1) #,
    jne .L3 #,

Même si c'est un cas plus simple.

Si vous changer la condition de la boucle interne à l' n < 4 puis gcc semble prêt à optimiser lors de l' k = 0.7. Comme indiqué dans les commentaires à la question, si le compilateur ne crois pas dérouler aidera ensuite, il risque d'être conservateur dans combien de dérouler, il va faire car il y a un code de taille de compromis.

Comme indiqué dans les commentaires, je suis en utilisant une version modifiée de l'OP du code de la godbolt des exemples, mais cela ne change pas la conclusion sous-jacente.

Remarque comme indiqué dans un commentaire ci-dessus , si nous les utilisons -fno-math-errno, qui s'arrête errno de, la gcc ne s'appliquent similaire d'optimisation.

1voto

Jordan Melo Points 483

En plus de Shafik Yaghmour réponse, je tiens à souligner que la raison de votre utilisation de l' volatile sur la variable num semble avoir aucun effet n'est qu' num est à lire avant d' func est même appelé. La lecture ne peut pas être optimisé à l'écart, mais l'appel de la fonction peut encore être optimisé à l'écart. Si vous avez déclaré le paramètre d' func à être une référence à l' volatile, c'est à dire. long double func(volatile int& num), cela pourrait empêcher le compilateur de l'optimisation de loin l'ensemble de l'appel d' func.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X