より効率的なものは何ですか?powを使用して正方形にするか、それ自体を乗算しますか?


119

これら2つの方法のうち、Cの方が効率的ですか?そして、どうですか:

pow(x,3)

x*x*x // etc?

9
あるx整数や浮動小数点は?
Matthew Flaschen、

6
上記の2つの操作を行うプログラムを作成して、プロファイリングライブラリを使用して実行にかかる時間を試してみることができます。これにより、実行時間の点で良い答えが得られます。
J.ポルファー、

3
効率的と言うとき、それは時間、またはスペース(つまり、メモリ使用量)を指していますか?
J. Polfer、

4
@sheepsimulator:+1は、クイックテストを作成すると、SOからあいまいまたは不正確な回答が得られるよりも速く決定的な回答が得られることを(再度)指摘するのに必要な時間を節約するために必要です。
私の正しい意見を

5
@kirill_igumそれらがバグではない浮動小数点値である場合、浮動小数点演算は結合的ではありません。
effeffe 2013年

回答:


82

私は間のパフォーマンスの違いをテストしたx*x*...pow(x,i)小型のためi、このコードを使用しました:

#include <cstdlib>
#include <cmath>
#include <boost/date_time/posix_time/posix_time.hpp>

inline boost::posix_time::ptime now()
{
    return boost::posix_time::microsec_clock::local_time();
}

#define TEST(num, expression) \
double test##num(double b, long loops) \
{ \
    double x = 0.0; \
\
    boost::posix_time::ptime startTime = now(); \
    for (long i=0; i<loops; ++i) \
    { \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
    } \
    boost::posix_time::time_duration elapsed = now() - startTime; \
\
    std::cout << elapsed << " "; \
\
    return x; \
}

TEST(1, b)
TEST(2, b*b)
TEST(3, b*b*b)
TEST(4, b*b*b*b)
TEST(5, b*b*b*b*b)

template <int exponent>
double testpow(double base, long loops)
{
    double x = 0.0;

    boost::posix_time::ptime startTime = now();
    for (long i=0; i<loops; ++i)
    {
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
    }
    boost::posix_time::time_duration elapsed = now() - startTime;

    std::cout << elapsed << " ";

    return x;
}

int main()
{
    using std::cout;
    long loops = 100000000l;
    double x = 0.0;
    cout << "1 ";
    x += testpow<1>(rand(), loops);
    x += test1(rand(), loops);

    cout << "\n2 ";
    x += testpow<2>(rand(), loops);
    x += test2(rand(), loops);

    cout << "\n3 ";
    x += testpow<3>(rand(), loops);
    x += test3(rand(), loops);

    cout << "\n4 ";
    x += testpow<4>(rand(), loops);
    x += test4(rand(), loops);

    cout << "\n5 ";
    x += testpow<5>(rand(), loops);
    x += test5(rand(), loops);
    cout << "\n" << x << "\n";
}

結果は次のとおりです。

1 00:00:01.126008 00:00:01.128338 
2 00:00:01.125832 00:00:01.127227 
3 00:00:01.125563 00:00:01.126590 
4 00:00:01.126289 00:00:01.126086 
5 00:00:01.126570 00:00:01.125930 
2.45829e+54

コンパイラーが最適化しないように、すべてのpow計算の結果を累積することに注意してください。

私がstd::pow(double, double)バージョンを使用している場合loops = 1000000l、私は次のようになります:

1 00:00:00.011339 00:00:00.011262 
2 00:00:00.011259 00:00:00.011254 
3 00:00:00.975658 00:00:00.011254 
4 00:00:00.976427 00:00:00.011254 
5 00:00:00.973029 00:00:00.011254 
2.45829e+52

これは、Ubuntu 9.10 64ビットを実行するIntel Core Duo上にあります。gcc 4.4.1と-o2最適化を使用してコンパイルされました。

したがって、Cでは、オーバーロードがないため、yesはよりx*x*xも高速になります。C ++では、大体同じです。(私のテストの方法論が正しいと仮定します。)pow(x, 3)pow(double, int)


これは、An Markmによるコメントへの応答です。

場合であってもusing namespace std指令が発行された2番目のパラメータがあれば、powであるint、そしてstd::pow(double, int)からの過負荷が<cmath>代わりに呼び出されます::pow(double, double)から<math.h>

このテストコードは、その動作を確認します。

#include <iostream>

namespace foo
{

    double bar(double x, int i)
    {
        std::cout << "foo::bar\n";
        return x*i;
    }


}

double bar(double x, double y)
{
    std::cout << "::bar\n";
    return x*y;
}

using namespace foo;

int main()
{
    double a = bar(1.2, 3); // Prints "foo::bar"
    std::cout << a << "\n";
    return 0;
}

1
これは、「using namespace std」を挿入するとCオプションが選択され、ランタイムに有害になるという意味ですか?
Andreas

両方のタイミングループで、powの計算はおそらく一度だけ行われます。gcc -O2は、ループ外のループ不変式を問題なく引き上げます。つまり、追加定数ループを乗算に変換するか、追加定数ループを最適化するだけで、コンパイラがどれだけうまく機能するかをテストしているだけです。書き出されたバージョンでも、ループが指数= 1と指数= 5で同じ速度である理由があります。
Peter Cordes、2015年

2
私はそれをgodboltで試しました(godboltにはBoostがインストールされていないため、タイミングはコメントアウトされています)。驚くことにstd::pow、を使用しない限り、実際には8 * loops回(指数> 2の場合)を呼び出します-fno-math-errno。その後、私が思ったように、pow呼び出しをループから外すことができます。私はerrnoがグローバルであるため、スレッドの安全性により、powを呼び出してerrnoを複数回設定する必要がある可能性があります... pow呼び出しは-O3..(- ffast-math、ループの外でも合計8を実行します。)
Peter Cordes

使用しているgodboltセッションで-ffast-mathがオンになっていることに気付く前に、私は反対票を投じました。それがなくても、testpow <1>とtestpow <2>は壊れpowています。ループから外れた呼び出しにインライン化されているため、そこに大きな欠陥があります。また、すべてのテストが同じ時間で実行されるため、FP追加のレイテンシを主にテストしているようです。あなたはtest5よりも遅いと予想しますtest1が、そうではありません。複数のアキュムレータを使用すると、依存関係チェーンが分割され、レイテンシが隠されます。
Peter Cordes

@PeterCordes、あなたは5年前にどこにいましたか?:-)私はpow、刻々と変化する値に適用することにより、ベンチマークを修正してみます(繰り返される捕虜式が持ち上げられるのを防ぐため)。
Emile Cormier、2015年

30

それは間違った種類の質問です。正しい質問は次のとおりです。「人間のコードを読む人にとって、どちらが理解しやすいのですか?」

速度が重要な場合は(後で)、尋ねずに測定してください。(そしてその前に、これを最適化することで実際に顕著な違いが生じるかどうかを測定します。)それまでは、最も読みやすいようにコードを記述してください。

編集
だけでは(それが既にされている必要がありますが)これを明確にする:画期的なスピードアップは、通常のようなものから来る、より良いアルゴリズムを使用してデータの局所性を向上させること動的メモリの使用量の削減結果を事前に計算し、など彼らはめったにから来ます単一の関数呼び出しをマイクロ最適化し、それらを行う場所非常に少ない場所です。これは、慎重な(そして時間のかかる)プロファイリングによってのみ検出されますが、非常に直感的でないことで高速化することはできません。もの(挿入するようなnoop ステートメント)、および1つのプラットフォームの最適化とは、別のプラットフォームの悲観化となる場合があります(環境を完全に把握していないため、質問するのではなく測定する必要があるのはこのためです)。

もう一度強調しておきます。そのようなことが重要ないくつかのアプリケーションでさえ、それらは使用されるほとんどの場所では重要ではなく、コードを見ても重要な場所を見つけることはほとんどありません。最初ホットスポット特定する必要があります。それ以外の場合、コードの最適化は時間の無駄に過ぎないためです。

1つの操作(ある値の2乗の計算など)がアプリケーションの実行時間の10%を占める場合でも(IMEは非常にまれです)、最適化してもその操作に必要な時間の50%を節約できます(IMEはさらに、はるかにまれですが)、アプリケーションの所要時間を5%短縮しました
ユーザーはそれに気づくためにストップウォッチが必要になります。(ほとんどの場合、20%未満の高速化はほとんどのユーザーに気付かれずに済むと思います。それが4つのそのようなスポットを見つける必要があります。)


43
それは正しい質問かもしれません。多分彼は彼自身の実際的なプロジェクトについて考えていませんが、
言語

137
Stackoverflowには、標準の免責事項を挿入するボタンが必要です。「私は時期尚早の最適化が悪であることをすでに知っていますが、学術目的でこの最適化の質問をしている、またはコードのその行/ブロックをボトルネックとしてすでに特定しています」。
Emile Cormier、

39
ここでは読みやすさは問題ではないと思います。x * xとpow(x、2)の記述はどちらも非常に明確に見えます。
KillianDS、

41
大胆でイタリック体を過度に使用すると、目にやさしくならない。
スタガス

24
私はこの答えに完全に同意しません。パフォーマンスについて質問するのは有効な質問です。あなたが達成できる最高のパフォーマンスは、時々有効な要件であり、しばしば誰かが他の言語ではなくc ++を使用した理由です。そして、測定は常に良い考えではありません。アイテムの数が非常に重要であることを知る背景がなく、後で1,000,000のアイテムでそれが非常に悪い選択であることがわかったので、10個のアイテムを使用してバブルソートとクイックソートを測定し、バブルソートをより速く見つけることができました。
jcoder 2013年

17

x*xまたは一般的なケースを処理する必要x*x*xがあるので、よりも高速になりますがpowpowx*x固有のものです。また、関数呼び出しなどを省略できます。

ただし、このようにマイクロ最適化している場合は、プロファイラーを入手して、本格的なプロファイリングを行う必要があります。圧倒的な確率は、この2つの違いに気付かないことです。


7
テストするまで同じことを考えていました。タイミングループでテストしたところ、x*x*x2倍std::pow(double base, int exponent)になっただけで、統計的に意味のあるパフォーマンスの違いがわかりません。
Emile Cormier、

2
コンパイラーによって最適化されていないことを確認してください。
Ponkadoodle、

1
@Emile:コンパイラーによって生成されたコードを確認します。オプティマイザは、トリッキーな(そして自明でない)ことを時々行います。また、さまざまな最適化レベル(-O0、-O1、-O2、-O3など)でパフォーマンスを確認します。
私の正しい意見だけ正しい2010年

2
一般化された関数が遅いと仮定することはできません。単純なコードの方がコンパイラーが最適化しやすいため、逆の場合もあります。
派手な

5

また、パフォーマンスの問題についても疑問に思っていたところ、@ EmileCormierからの回答に基づいて、これがコンパイラーによって最適化されることを望んでいました。ただし、彼が示したテストコードを使用すると、コンパイラーがstd :: pow()呼び出しを最適化できるようになるのではないかと心配しました。毎回同じ値が呼び出しで使用され、コンパイラーが結果を保存してループでそれを再利用します-これは、すべてのケースでほぼ同じランタイムを説明します。だから私もそれを調べました。

これが私が使ったコードです(test_pow.cpp):

#include <iostream>                                                                                                                                                                                                                       
#include <cmath>
#include <chrono>

class Timer {
  public:
    explicit Timer () : from (std::chrono::high_resolution_clock::now()) { }

    void start () {
      from = std::chrono::high_resolution_clock::now();
    }

    double elapsed() const {
      return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - from).count() * 1.0e-6;
    }

  private:
    std::chrono::high_resolution_clock::time_point from;
};

int main (int argc, char* argv[])
{
  double total;
  Timer timer;



  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += std::pow (i,2);
  std::cout << "std::pow(i,2): " << timer.elapsed() << "s (result = " << total << ")\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += i*i;
  std::cout << "i*i: " << timer.elapsed() << "s (result = " << total << ")\n";

  std::cout << "\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += std::pow (i,3);
  std::cout << "std::pow(i,3): " << timer.elapsed() << "s (result = " << total << ")\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += i*i*i;
  std::cout << "i*i*i: " << timer.elapsed() << "s (result = " << total << ")\n";


  return 0;
}

これは以下を使用してコンパイルされました:

g++ -std=c++11 [-O2] test_pow.cpp -o test_pow

基本的に、違いはstd :: pow()の引数がループカウンターです。恐れていたように、パフォーマンスの違いは顕著です。-O2フラグを指定しないと、私のシステム(Arch Linux 64ビット、g ++ 4.9.1、Intel i7-4930)での結果は次のようになります。

std::pow(i,2): 0.001105s (result = 3.33333e+07)
i*i: 0.000352s (result = 3.33333e+07)

std::pow(i,3): 0.006034s (result = 2.5e+07)
i*i*i: 0.000328s (result = 2.5e+07)

最適化により、結果は同様に印象的でした:

std::pow(i,2): 0.000155s (result = 3.33333e+07)
i*i: 0.000106s (result = 3.33333e+07)

std::pow(i,3): 0.006066s (result = 2.5e+07)
i*i*i: 9.7e-05s (result = 2.5e+07)

したがって、コンパイラは少なくともstd :: pow(x、2)の場合を最適化しようとしますが、std :: pow(x、3)の場合は最適化しないようです(std :: powよりも約40倍時間がかかります) (x、2)ケース)。すべてのケースで、手動拡張の方が優れていましたが、特にパワー3の場合(60倍高速)です。タイトなループで2より大きい整数の累乗でstd :: pow()を実行する場合、これは間違いなく覚えておく価値があります...


4

最も効率的な方法は、乗算の指数関数的な増加を考慮することです。このコードでp ^ qを確認します。

template <typename T>
T expt(T p, unsigned q){
    T r =1;
    while (q != 0) {
        if (q % 2 == 1) {    // if q is odd
            r *= p;
            q--;
        }
        p *= p;
        q /= 2;
    }
    return r;
}

2

指数が一定で小さい場合は、それを拡張して乗算の数を最小限に抑えます。(例えば、x^4最適ではないですx*x*x*xが、y*yどこy=x*x。そして、x^5あるy*y*x場所y=x*xなど。)一定の整数の指数については、単に既に最適化されたフォームを書き出します。小さな指数の場合、これは、コードがプロファイルされているかどうかにかかわらず実行する必要がある標準的な最適化です。最適化されたフォームは、基本的に常に実行する価値があるほど多くのケースで高速になります。

(Visual C ++を使用している場合std::pow(float,int)、私が言及している最適化を実行します。これにより、操作のシーケンスは指数のビットパターンに関連します。ただし、コンパイラーがループを展開することは保証しません。そのため、実行する価値はあります。手でそれを。)

[編集] BTWにpowは、プロファイラーの結果を表示するという驚くべき傾向があります。絶対に必要ではなく(つまり、指数が大きいか、定数ではない)、パフォーマンスにまったく不安がある場合は、最適なコードを記述し、プロファイラーがそれを通知するのを待つのが最善です(意外にも) )さらに考える前に時間を無駄にする。(代わりに、呼び出しpowてプロファイラーに(当然のことながら)時間を浪費していることを通知させる-インテリジェントに行うことで、このステップを省略できます。)


0

私は同様の問題で忙しくしており、その結果にはかなり困惑しています。私はn体の状況でニュートン重力のx⁻³/²を計算していました(加速度は距離ベクトルdにある別の質量体Mから受けました):(a = M G d*(d²)⁻³/²ここで、d²はd自体のドット(スカラー)積です)、そして、私は計算M*G*pow(d2, -1.5)がより簡単になると思いましたM*G/d2/sqrt(d2)

トリックは、小さなシステムにも当てはまりますが、システムのサイズM*G/d2/sqrt(d2)が大きくなるにつれて効率が向上し、システムのサイズがこの結果に影響を与える理由がわかりません。異なるデータに対して操作を繰り返しても影響がないためです。システムが成長するにつれて最適化が可能であったかのようですが、これはpow

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.