web-dev-qa-db-ja.com

x doubleとともにx * xの代わりにpow(x、2)を使用する利点はありますか?

このコードを使用する利点はありますか

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

これの代わりに?

double x;
double square = x*x;

私はx * xを好み、実装を見ると(Microsoft)x * xは特定のスクエアケースではpowより単純なので、powには利点がありません。

捕虜が優れている特定のケースはありますか?

44

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最適になることはありません。

54
Alnitak

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番目のルール(専門家のみ!):まだやらないでください。

24
Sebastian Mach

_x*x_が明確であるだけでなく、少なくともpow(x,2)と同じくらい速くなります。

13
David Heffernan

この質問は、科学的プログラミングに関するCおよびC++のほとんどの実装の重要な弱点の1つに触れています。 FortranからCに約20年、そしてその後C++に切り替えた後、これは、その切り替えが良いことかどうか疑問に思うことがある、痛みのある場所の1つです。

要するに問題:

  • powを実装する最も簡単な方法はType pow(Type x; Type y) {return exp(y*log(x));}です
  • ほとんどのCおよびC++コンパイラは、簡単な方法を採用しています。
  • 「正しいことをする」人もいるかもしれませんが、高い最適化レベルでのみです。
  • _x*x_と比較すると、pow(x,2)を使用した簡単な方法は計算量が非常に多く、精度が低下します。

科学プログラミングを目的とした言語と比較してください。

  • pow(x,y)と書かないでください。これらの言語には、組み込みのべき乗演算子があります。 CとC++が累乗演算子を実装することを断固として拒否したことは、多くの科学プログラマーのプログラマーの血を沸騰させます。頑固なFortranプログラマーにとっては、これだけがCに切り替えない理由です。
  • Fortran(および他の言語)は、すべての小さな整数の累乗に対して「正しいことを行う」ために必要です。ここで、smallは-12〜12の整数です(「正しいことを行う」ことができない場合、コンパイラは非準拠です) 。)さらに、最適化をオフにして行う必要があります。
  • 多くのFortranコンパイラーは、簡単な方法に頼らずに合理的な根を抽出する方法も知っています。

「正しいことをする」ために高い最適化レベルに依存することには問題があります。私は、セーフティクリティカルなソフトウェアの最適化の使用を禁止している複数の組織で働いてきました。メモリーは、最適化コンパイラーのバグが原因で、ここで1,000万ドル、そこで1億ドルを失った後、非常に長くなる可能性があります(数十年)。

私見、決して CまたはC++でpow(x,2)を使用する必要があります。この意見は私だけではありません。 pow(x,2)を使用するプログラマーは通常、コードレビュー中に大きな時間を費やします。

10
David Hammen

C++ 11では、std::pow(x,2)よりもx * xを使用するほうが有利な場合が1つあり、その場合は constexpr

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

ご覧のとおり、 std :: powconstexprとマークされていないため、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の今後のリリースで変更される可能性があることに注意してください。

9
Shafik Yaghmour

_x * x_は常に単純な乗算にコンパイルされます。 pow(x, 2)は、同じように最適化される可能性がありますが、決して保証されません。最適化されていない場合は、低速の一般的な累乗の数学ルーチンを使用している可能性があります。したがって、パフォーマンスが懸念される場合は、常に_x * x_を優先する必要があります。

7
AshleysBrain

私見では:

  • コードの読みやすさ
  • コードの堅牢性-pow(x, 6)への変更が容易になり、特定のプロセッサ用の浮動小数点メカニズムが実装されるなど.
  • パフォーマンス-これを計算するよりスマートで高速な方法がある場合(アセンブラーまたは何らかの特殊なトリックを使用)、powはそれを行います。あなたはしません.. :)

乾杯

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_アプローチでは、includeing cmathを必要としません。これにより、他のすべてが等しい場合にコンパイルが非常に速くなります。

1
Escualo