web-dev-qa-db-fra.com

Y a-t-il un avantage à utiliser pow (x, 2) au lieu de x * x, avec x double?

y a-t-il un avantage à utiliser ce code

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

au lieu de cela?

double x;
double square = x*x;

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

Y a-t-il un cas particulier où la poudre est supérieure?

44

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

x = x * x;

et

y = pow(y, 2);

donne le même Code d'assemblage:

#include <cmath>

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

Se réunit pour:

    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

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

54
Alnitak

std :: pow est plus expressif si vous voulez dire x², x x est plus expressif si vous voulez dire x x, surtout si vous codez simplement par ex. un article scientifique et les lecteurs devraient être en mesure de comprendre votre mise en œuvre par rapport à l'article. La différence est subtile peut-être pour x * x/x², mais je pense que si vous utilisez des fonctions nommées en général, cela augmente l'exessibilité du code et la lisibilité.

Sur les compilateurs modernes, comme par ex. g ++ 4.x, std :: pow (x, 2) sera inséré, si ce n'est même pas un compilateur intégré, et réduit en force à x * x. Si ce n'est pas le cas par défaut et que vous ne vous souciez pas de la conformité du type flottant IEEE, consultez le manuel de votre compilateur pour un commutateur mathématique rapide (g ++ == -ffast-math).


Sidenote: Il a été mentionné que l'inclusion de math.h augmente la taille du programme. Ma réponse a été:

En C++, vous #include <cmath>, pas math.h. De plus, si votre compilateur n'est pas obsolète, il n'augmentera la taille de vos programmes que par ce que vous utilisez (dans le cas général), et si votre implication de std :: pow se contente de suivre les instructions x87 correspondantes et un g ++ moderne va réduire la force x² avec x * x, il n'y a donc pas d'augmentation de taille pertinente. De plus, la taille du programme ne doit jamais, jamais dicter à quel point vous exprimez votre code.

Un autre avantage de cmath sur math.h est qu'avec cmath, vous obtenez une surcharge std :: pow pour chaque type à virgule flottante, alors qu'avec math.h vous obtenez pow, powf, etc. dans l'espace de noms global, donc cmath augmente l'adaptabilité de code, en particulier lors de l'écriture de modèles.

En règle générale: préférez un code expressif et clair à des performances douteuses et à un code raisonné de taille binaire.

Voir aussi Knuth:

"Il faut oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal"

et Jackson:

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

24
Sebastian Mach

Non seulement x*x Est plus clair, mais il sera certainement au moins aussi rapide que pow(x,2).

13
David Heffernan

Cette question touche à l'une des principales faiblesses de la plupart des implémentations de C et C++ concernant la programmation scientifique. Après avoir passé de Fortran à C pendant une vingtaine d'années, puis à C++, cela reste l'un de ces points douloureux qui me fait parfois me demander si ce changement était une bonne chose à faire.

Le problème en bref:

  • La façon la plus simple d'implémenter pow est Type pow(Type x; Type y) {return exp(y*log(x));}
  • La plupart des compilateurs C et C++ prennent la solution de facilité.
  • Certains pourraient "faire la bonne chose", mais uniquement à des niveaux d'optimisation élevés.
  • Comparé à x*x, La solution de facilité avec pow(x,2) est extrêmement coûteuse en termes de calcul et perd de sa précision.

Comparer aux langages destinés à la programmation scientifique:

  • Vous n'écrivez pas pow(x,y). Ces langages ont un opérateur d'exponentiation intégré. Que C et C++ aient fermement refusé d'implémenter un opérateur d'exponentiation fait bouillir le sang de nombreux programmeurs scientifiques. Pour certains programmeurs purs et durs de Fortran, c'est à lui seul une raison pour ne jamais passer au C.
  • Fortran (et d'autres langages) sont tenus de `` faire la bonne chose '' pour tous les petits pouvoirs entiers, où petit est tout entier compris entre -12 et 12. (Le compilateur n'est pas conforme s'il ne peut pas `` faire la bonne chose '') .) De plus, ils sont tenus de le faire avec l'optimisation désactivée.
  • De nombreux compilateurs Fortran savent également extraire des racines rationnelles sans recourir à la solution de facilité.

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 dans les logiciels critiques pour la sécurité. Les mémoires peuvent être très longues (plusieurs décennies) après avoir perdu 10 millions de dollars ici, 100 millions là-bas, tout cela en raison 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 seul dans cette opinion. Les programmeurs qui utilisent pow(x,2) obtiennent généralement beaucoup de temps lors des révisions de code.

10
David Hammen

En C++ 11, il y a un cas où il y a un avantage à utiliser x * x Par rapport à std::pow(x,2) et ce cas est où vous devez 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 donc il est inutilisable dans un fonction constexpr .

Sinon, du point de vue des performances, mettre le 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 tout compilateur moderne.

Il convient de noter qu'actuellement gcc considère pow un constexpr , également couvert ici mais il s'agit d'une extension non conforme et ne doit pas être invoquée et changera probablement dans les versions ultérieures de gcc.

9
Shafik Yaghmour

x * x Sera toujours compilé en une simple multiplication. pow(x, 2) est susceptible, mais en aucun cas garanti, d'être optimisé de la même manière. S'il n'est pas optimisé, il utilise probablement une routine mathématique générale de montée en puissance lente. Donc, si la performance est votre préoccupation, vous devriez toujours privilégier x * x.

7
AshleysBrain

A MON HUMBLE AVIS:

  • Lisibilité du code
  • Robustesse du code - il sera plus facile de passer à pow(x, 6), peut-être qu'un mécanisme à virgule flottante pour un processeur spécifique est implémenté, etc.
  • Performance - s'il existe un moyen plus intelligent et plus rapide de calculer cela (en utilisant l'assembleur ou une sorte de truc spécial), pow le fera. vous ne serez pas .. :)

À votre santé

6
Hertzel Guinness

Je choisirais probablement std::pow(x, 2) car cela pourrait faciliter la refactorisation de mon code. Et cela ne ferait aucune différence une fois le code optimisé.

Maintenant, les deux approches ne sont pas identiques. Voici 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 sur la sortie Assembly, 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 de -std=c++11, Je l'utilise toujours.

Premièrement: lors du débogage (avec zéro optimisation), 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    $16, %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    $16, %rsp
    movsd   %xmm0, -8(%rbp)
# 9 "square.cpp" 1
    ### Square Library
# 0 "" 2
    movq    -8(%rbp), %rax
    movl    $2, %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, sauf si 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 les 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 (L'optimisation la plus basse), l'assemblage est à nouveau identique:

# 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    $8, %rsp
LCFI3:
    popq    %rbx
LCFI4:
    popq    %rbp
LCFI5:
    ret
LFE239:
    .globl __Z15implicit_harderd
__Z15implicit_harderd:
LFB240:
    pushq   %rbp
LCFI6:
    pushq   %rbx
LCFI7:
    subq    $8, %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    $8, %rsp
LCFI9:
    popq    %rbx
LCFI10:
    popq    %rbp
LCFI11:
    ret

Enfin: l'approche x * x Ne nécessite pas includeing cmath ce qui rendrait votre compilation un peu plus rapide toutes choses égales par ailleurs.

1
Escualo