このコードを使用する利点はありますか
double x;
double square = pow(x,2);
これの代わりに?
double x;
double square = x*x;
私はx * xを好み、実装を見ると(Microsoft)x * xは特定のスクエアケースではpowより単純なので、powには利点がありません。
捕虜が優れている特定のケースはありますか?
FWIW、MacOS X 10.6上のgcc-4.2および_-O3
_コンパイラフラグ、
_x = x * x;
_
そして
_y = pow(y, 2);
_
同じアセンブリコードの結果:
_#include <cmath>
void test(double& x, double& y) {
x = x * x;
y = pow(y, 2);
}
_
組み立て先:
_ 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
_
したがって、適切なコンパイラーを使用している限り、アプリケーションにとって意味のある方を記述してください。ただし、pow(x, 2)
は、単純な乗算よりもmore最適になることはありません。
std :: powは、x²、x -xは、x xを意味する場合、特に表現力が豊かです。科学論文と読者は、あなたの実装と論文を理解できるはずです。 x * x /x²の違いは微妙かもしれませんが、一般的に名前付き関数を使用すると、コードの使いやすさと読みやすさが向上すると思います。
たとえば、最新のコンパイラではg ++ 4.x、std :: pow(x、2)は、コンパイラ組み込みでなく、x * xに強度が低下している場合、インライン化されます。デフォルトではなく、IEEE浮動型の適合性を気にしない場合は、コンパイラのマニュアルで高速数学スイッチ(g ++ == -ffast-math)を確認してください。
サイドノート:math.hをインクルードすると、プログラムのサイズが増加することが言及されています。私の答えは:
C++では、
#include <cmath>
、math.hではない。また、コンパイラが古いものではない場合、使用しているもの(一般的な場合)によってのみプログラムのサイズが増加し、std :: powの実装が対応するx87命令と最新のg ++だけにインライン化された場合x * xでx²の強度が低下しますが、関連するサイズの増加はありません。また、プログラムのサイズは、コードをどの程度表現力豊かにするかを決して決定すべきではありません。
Math.hに対するcmathのさらなる利点は、cmathを使用すると、各浮動小数点型に対してstd :: powオーバーロードが取得されるのに対し、math.hを使用するとグローバル名前空間でpow、powfなどが取得されるため、cmathが適応性を向上させることです特にテンプレートを書くときのコードの数。
一般的なルールとして:疑わしい根拠のあるパフォーマンスとバイナリサイズの理由コードよりも表現力豊かで明確なコードを好む。
Knuthも参照してください。
「私たちは小さな効率を忘れて、約97%の時間を言うべきです。時期尚早な最適化はすべての悪の根源です」
ジャクソン:
プログラム最適化の最初のルール:しないでください。プログラム最適化の2番目のルール(専門家のみ!):まだやらないでください。
_x*x
_が明確であるだけでなく、少なくともpow(x,2)
と同じくらい速くなります。
この質問は、科学的プログラミングに関するCおよびC++のほとんどの実装の重要な弱点の1つに触れています。 FortranからCに約20年、そしてその後C++に切り替えた後、これは、その切り替えが良いことかどうか疑問に思うことがある、痛みのある場所の1つです。
要するに問題:
pow
を実装する最も簡単な方法はType pow(Type x; Type y) {return exp(y*log(x));}
ですx*x
_と比較すると、pow(x,2)
を使用した簡単な方法は計算量が非常に多く、精度が低下します。科学プログラミングを目的とした言語と比較してください。
pow(x,y)
と書かないでください。これらの言語には、組み込みのべき乗演算子があります。 CとC++が累乗演算子を実装することを断固として拒否したことは、多くの科学プログラマーのプログラマーの血を沸騰させます。頑固なFortranプログラマーにとっては、これだけがCに切り替えない理由です。「正しいことをする」ために高い最適化レベルに依存することには問題があります。私は、セーフティクリティカルなソフトウェアの最適化の使用を禁止している複数の組織で働いてきました。メモリーは、最適化コンパイラーのバグが原因で、ここで1,000万ドル、そこで1億ドルを失った後、非常に長くなる可能性があります(数十年)。
私見、決して CまたはC++でpow(x,2)
を使用する必要があります。この意見は私だけではありません。 pow(x,2)
を使用するプログラマーは通常、コードレビュー中に大きな時間を費やします。
C++ 11では、std::pow(x,2)
よりもx * x
を使用するほうが有利な場合が1つあり、その場合は constexpr :
constexpr double mySqr( double x )
{
return x * x ;
}
ご覧のとおり、 std :: pow はconstexprとマークされていないため、constexpr関数。
それ以外の場合、パフォーマンスの観点から、次のコードを godbolt に配置すると、これらの関数が表示されます。
#include <cmath>
double mySqr( double x )
{
return x * x ;
}
double mySqr2( double x )
{
return std::pow( x, 2.0 );
}
同一のアセンブリを生成します:
mySqr(double):
mulsd %xmm0, %xmm0 # x, D.4289
ret
mySqr2(double):
mulsd %xmm0, %xmm0 # x, D.4292
ret
また、最新のコンパイラからも同様の結果が得られるはずです。
現在、 gccはpowをconstexprと見なします 、また here ですが、これは非準拠の拡張機能であり、gcc
の今後のリリースで変更される可能性があることに注意してください。
_x * x
_は常に単純な乗算にコンパイルされます。 pow(x, 2)
は、同じように最適化される可能性がありますが、決して保証されません。最適化されていない場合は、低速の一般的な累乗の数学ルーチンを使用している可能性があります。したがって、パフォーマンスが懸念される場合は、常に_x * x
_を優先する必要があります。
私見では:
pow(x, 6)
への変更が容易になり、特定のプロセッサ用の浮動小数点メカニズムが実装されるなど.乾杯
コードをリファクタリングしやすくするため、おそらくstd::pow(x, 2)
を選択します。そして、コードが最適化されても、何の違いもありません。
現在、2つのアプローチは同一ではありません。これは私のテストコードです。
_#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);
}
_
asm("text");
呼び出しは、Assembly出力にコメントを書き込むだけです(OS X 10.7.4のGCC 4.8.1)。
_g++ example.cpp -c -S -std=c++11 -O[0, 1, 2, or 3]
_
_-std=c++11
_は必要ありません。私は常にそれを使用しています。
最初:デバッグ時(最適化なし)、生成されるアセンブリは異なります。これは関連する部分です。
_# 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
_
ただし、最適化されたコードを生成する場合(GCCの最低の最適化レベル、つまり_-O1
_であっても)は、コードはまったく同じです。
_# 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
_
したがって、最適化されていないコードの速度を気にしない限り、実際には違いはありません。
私が言ったように:std::pow(x, 2)
はあなたの意図をより明確に伝えているように思えますが、それはパフォーマンスではなく好みの問題です。
そして、最適化はより複雑な式に対しても成り立つようです。例えば:
_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));
}
_
繰り返しますが、_-O1
_(最低の最適化)を使用すると、アセンブリは再び同一になります。
_# 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
_
最後に:_x * x
_アプローチでは、include
ing cmath
を必要としません。これにより、他のすべてが等しい場合にコンパイルが非常に速くなります。