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?
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.
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.
Non seulement x*x
plus cela sera certainement au moins aussi rapide que pow(x,2)
.
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
estType 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 avecpow(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.
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
.
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
.
à 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
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.