Est-il un avantage à utiliser pow(x,2) au lieu de x*x, avec x en double?

est-il un avantage à utiliser ce code

double x;
double square = pow(x,2);

au lieu de ça?

double x;
double square = x*x;

je préfère x*x et en regardant mon implémentation (Microsoft) Je ne trouve aucun avantage à pow parce que x*x est plus simple que pow pour le cas carré particulier.

y a-t-il un cas particulier où le PG est supérieur?

43
demandé sur Lightness Races in Orbit 2011-06-12 13:18:15

8 réponses

FWIW, avec gcc-4.2 sur MacOS X 10.6 et -O3 compilateur drapeaux,

x = x * x;

et

y = pow(y, 2);

résultat du même code d'assemblage:

#include <cmath>

void test(double& x, double& y) {
        x = x * x;
        y = pow(y, 2);
}

se monte à:

    pushq   %rbp
    movq    %rsp, %rbp
    movsd   (%rdi), %xmm0
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, (%rdi)
    movsd   (%rsi), %xmm0
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, (%rsi)
    leave
    ret

aussi longtemps que vous utilisez un compilateur décent, écrivez ce qui a le plus de sens pour votre application, mais considérez que pow(x, 2) ne peut jamais être plus optimal que la simple multiplication.

54
répondu Alnitak 2016-06-14 16:25:40

std::pow est plus expressive si tu veux x2, x x est plus expressive si tu veux dire x x, surtout si vous êtes juste de codage vers le bas par exemple, un document scientifique et le lecteur devrait être en mesure de comprendre votre mise en œuvre contre le papier. La différence est subtile peut-être pour x*x/x2, mais je pense que si vous utilisez des fonctions nommées en général, cela augmente l'expectative du code et la lisibilité.

sur les compilateurs modernes, comme par exemple g++ 4.x, std::pow(x,2) sera inlined, si ce n'est même pas un compilateur-builtin, et strength-reduced à x*X. Si ce n'est pas par défaut et que vous ne vous souciez pas de la conformité de type flottant IEEE, vérifiez le manuel de votre compilateur pour un commutateur rapide (g++ == -ffast-math).


Sidenote: il a été mentionné qu'y compris les mathématiques.h augmente la taille du programme. Ma réponse fut:

En C++, vous #include <cmath> , pas de mathématiques.h . Aussi, si votre compilateur n'est pas pierre-vieux, il va augmenter la taille de vos programmes que par ce que vous utilisez (dans le cas général), et si votre implantation de std::pow vient de s'aligner aux instructions correspondantes x87, et un G++ moderne va renforcer-réduire x2 avec x*x, alors il n'y a pas de taille-augmenter. En outre, la taille du programme ne devrait jamais, jamais dicter comment expressif vous faites votre code est.

un avantage supplémentaire de cmath sur les maths.h est qu'avec cmath, vous obtenez une MST:: pow surcharge pour chaque type de point flottant, alors qu'en mathématiques.h vous obtenez pow, powf,etc. dans l'espace de noms global, donc cmath augmenter l'adaptabilité du code, en particulier lors de l'écriture de gabarits.

en règle générale: préfèrent le code expressif et clair à la performance basée sur des bases douteuses et le code binaire motivé par la taille.

Voir Aussi Knuth:

" nous devrions oublier les petites efficacités, dire environ 97% de l'optimisation prématurée est la racine de tout mal"

et Jackson:

la première règle de L'optimisation de programme: ne le faites pas. La deuxième règle de L'optimisation de programme (pour les experts seulement!): Ne le faites pas encore.

22
répondu Sebastian Mach 2014-12-15 09:01:48

Non seulement x*x plus cela sera certainement au moins aussi rapide que pow(x,2) .

13
répondu David Heffernan 2011-06-12 09:20:39

cette question touche à l'une des principales faiblesses de la plupart des implémentations de C et C++ en ce qui concerne la programmation scientifique. Après être passé de Fortran à C environ vingt ans, et plus tard à C++, cela reste un de ces points sensibles qui me fait parfois me demander si ce changement était une bonne chose à faire.

Le problème en un mot:

  • la façon la plus facile de mettre en œuvre pow est Type pow(Type x; Type y) {return exp(y*log(x));}
  • la plupart des compilateurs C et c++ prennent la voie facile.
  • Certains pourraient "faire la bonne chose", mais seulement à des niveaux d'optimisation.
  • par rapport à x*x , le moyen facile de sortir avec pow(x,2) est extrêmement coûteux en termes de calcul et perd la précision.

comparez aux langues destinées à la programmation scientifique:

  • vous n'écrivez pas pow(x,y) . Ces langues un haut-opérateur exponentiel. Le fait que C et c++ aient constamment refusé d'implémenter un opérateur d'exponentiation fait bouillir le sang de nombreux programmeurs scientifiques. Pour certains programmeurs Fortran diehard, c'est la seule raison de ne jamais passer en C.
  • Fortran (et autres langues) sont tenus de "faire la bonne chose" pour toutes les petites puissances entières, où de petites est un entier compris entre -12 et 12. (Le compilateur n'est pas conforme s'il ne peut pas faire le bon choix chose".) De plus, ils sont tenus de le faire sans optimisation.
  • beaucoup de compilateurs Fortran savent aussi extraire des racines rationnelles sans recourir à la solution facile.

il y a un problème à compter sur des niveaux d'optimisation élevés pour "faire la bonne chose". J'ai travaillé pour plusieurs organisations qui ont interdit l'utilisation de l'optimisation de la sécurité des logiciels critiques. Les souvenirs peuvent être très longs (plusieurs décennies) après la perte 10 millions de dollars ici, 100 millions là, tout ça à cause de bugs dans certains compilateurs d'optimisation.

à mon humble avis, on devrait jamais utiliser pow(x,2) en C ou C++. Je ne suis pas le seul dans cette opinion. Les programmeurs qui n'utilisent pow(x,2) typiquement se reamed grand temps pendant les revues de code.

9
répondu David Hammen 2011-06-12 11:58:47

En C++11, il est un cas où il y a un avantage à l'utilisation de x * x plus std::pow(x,2) et c'est où vous avez besoin de l'utiliser dans un constexpr :

constexpr double  mySqr( double x )
{
      return x * x ;
}

comme nous pouvons le voir std::pow n'est pas marqué constexpr et il est donc inutilisable dans un constexpr fonction.

autrement d'une prestation la mise en perspective du code suivant dans godbolt montre ces fonctions:

#include <cmath>

double  mySqr( double x )
{
      return x * x ;
}

double  mySqr2( double x )
{
      return std::pow( x, 2.0 );
}

générer un assemblage identique:

mySqr(double):
    mulsd   %xmm0, %xmm0    # x, D.4289
    ret
mySqr2(double):
    mulsd   %xmm0, %xmm0    # x, D.4292
    ret

et nous devrions nous attendre à des résultats similaires de n'importe quel compilateur moderne.

vaut la peine de noter qu'actuellement gcc considère pow un constexpr , également couvert ici mais il s'agit d'une extension non conforme et ne devrait pas être dépend et changera probablement dans les versions ultérieures de gcc .

8
répondu Shafik Yaghmour 2017-05-23 12:31:55

x * x compilera toujours à une simple multiplication. pow(x, 2) est susceptible, mais en aucun cas garanti, d'être optimisé à la même. Si ce n'est pas optimisé, c'est probablement en utilisant une lente routine de mathématiques. Donc, si la performance est votre préoccupation, vous devriez toujours favorable x * x .

7
répondu AshleysBrain 2011-06-12 15:54:36

à mon humble avis:

  • lisibilité du Code
  • la robustesse du Code - sera plus facile à changer en pow(x, 6) , peut-être un mécanisme flottant pour un processeur spécifique est mis en œuvre, etc.
  • Performance-s'il y a une façon plus intelligente et plus rapide de calculer cela (en utilisant un assembleur ou une sorte de truc spécial), pow le fera. vous ne serez pas.. :)

Cheers

6
répondu Hertzel Guinness 2011-06-12 10:21:26

je choisirais probablement std::pow(x, 2) parce que cela pourrait rendre mon remaniement de code plus facile. Et cela ne ferait aucune différence une fois le code optimisé.

Maintenant, les deux approches ne sont pas identiques. C'est mon code de test:

#include<cmath>

double square_explicit(double x) {
  asm("### Square Explicit");
  return x * x;
}

double square_library(double x) {
  asm("### Square Library");  
  return std::pow(x, 2);
}

l'appel asm("text"); écrit simplement des commentaires à la sortie d'assemblage, que je produis en utilisant (GCC 4.8.1 sur OS X 10.7.4):

g++ example.cpp -c -S -std=c++11 -O[0, 1, 2, or 3]

vous n'avez pas besoin -std=c++11 , Je l'utilise toujours.

tout D'abord: lors du débogage (avec optimisation zéro), l'assemblage produit est différent; c'est la partie pertinente:

# 4 "square.cpp" 1
    ### Square Explicit
# 0 "" 2
    movq    -8(%rbp), %rax
    movd    %rax, %xmm1
    mulsd   -8(%rbp), %xmm1
    movd    %xmm1, %rax
    movd    %rax, %xmm0
    popq    %rbp
LCFI2:
    ret
LFE236:
    .section __TEXT,__textcoal_nt,coalesced,pure_instructions
    .globl __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_
    .weak_definition __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_
__ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_:
LFB238:
    pushq   %rbp
LCFI3:
    movq    %rsp, %rbp
LCFI4:
    subq    , %rsp
    movsd   %xmm0, -8(%rbp)
    movl    %edi, -12(%rbp)
    cvtsi2sd    -12(%rbp), %xmm2
    movd    %xmm2, %rax
    movq    -8(%rbp), %rdx
    movd    %rax, %xmm1
    movd    %rdx, %xmm0
    call    _pow
    movd    %xmm0, %rax
    movd    %rax, %xmm0
    leave
LCFI5:
    ret
LFE238:
    .text
    .globl __Z14square_libraryd
__Z14square_libraryd:
LFB237:
    pushq   %rbp
LCFI6:
    movq    %rsp, %rbp
LCFI7:
    subq    , %rsp
    movsd   %xmm0, -8(%rbp)
# 9 "square.cpp" 1
    ### Square Library
# 0 "" 2
    movq    -8(%rbp), %rax
    movl    , %edi
    movd    %rax, %xmm0
    call    __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_
    movd    %xmm0, %rax
    movd    %rax, %xmm0
    leave
LCFI8:
    ret

mais lorsque vous produisez le code optimisé (même au niveau d'optimisation le plus bas pour GCC, ce qui signifie -O1 ) le code est juste identique:

# 4 "square.cpp" 1
    ### Square Explicit
# 0 "" 2
    mulsd   %xmm0, %xmm0
    ret
LFE236:
    .globl __Z14square_libraryd
__Z14square_libraryd:
LFB237:
# 9 "square.cpp" 1
    ### Square Library
# 0 "" 2
    mulsd   %xmm0, %xmm0
    ret

donc, cela ne fait vraiment aucune différence à moins que vous vous souciez de la vitesse du code non optimisé.

comme je l'ai dit: il me semble que std::pow(x, 2) exprime plus clairement vos intentions, mais c'est une question de préférence, pas de performance.

et l'optimisation semble tenir même pour des expressions plus complexes. Prenez, par exemple:

double explicit_harder(double x) {
  asm("### Explicit, harder");
  return x * x - std::sin(x) * std::sin(x) / (1 - std::tan(x) * std::tan(x));
}

double implicit_harder(double x) {
  asm("### Library, harder");
  return std::pow(x, 2) - std::pow(std::sin(x), 2) / (1 - std::pow(std::tan(x), 2));
}

encore une fois, avec -O1 (la plus basse optimisation), l'assemblage est identique encore une fois:

# 14 "square.cpp" 1
    ### Explicit, harder
# 0 "" 2
    call    _sin
    movd    %xmm0, %rbp
    movd    %rbx, %xmm0
    call    _tan
    movd    %rbx, %xmm3
    mulsd   %xmm3, %xmm3
    movd    %rbp, %xmm1
    mulsd   %xmm1, %xmm1
    mulsd   %xmm0, %xmm0
    movsd   LC0(%rip), %xmm2
    subsd   %xmm0, %xmm2
    divsd   %xmm2, %xmm1
    subsd   %xmm1, %xmm3
    movapd  %xmm3, %xmm0
    addq    , %rsp
LCFI3:
    popq    %rbx
LCFI4:
    popq    %rbp
LCFI5:
    ret
LFE239:
    .globl __Z15implicit_harderd
__Z15implicit_harderd:
LFB240:
    pushq   %rbp
LCFI6:
    pushq   %rbx
LCFI7:
    subq    , %rsp
LCFI8:
    movd    %xmm0, %rbx
# 19 "square.cpp" 1
    ### Library, harder
# 0 "" 2
    call    _sin
    movd    %xmm0, %rbp
    movd    %rbx, %xmm0
    call    _tan
    movd    %rbx, %xmm3
    mulsd   %xmm3, %xmm3
    movd    %rbp, %xmm1
    mulsd   %xmm1, %xmm1
    mulsd   %xmm0, %xmm0
    movsd   LC0(%rip), %xmm2
    subsd   %xmm0, %xmm2
    divsd   %xmm2, %xmm1
    subsd   %xmm1, %xmm3
    movapd  %xmm3, %xmm0
    addq    , %rsp
LCFI9:
    popq    %rbx
LCFI10:
    popq    %rbp
LCFI11:
    ret

enfin: le x * x approche ne nécessite pas include ing cmath qui rendrait votre compilation un peu plus rapide tout le reste étant égal.

1
répondu Escualo 2013-10-14 19:59:00