これら2つの方法のうち、Cの方が効率的ですか?そして、どうですか:
pow(x,3)
対
x*x*x // etc?
これら2つの方法のうち、Cの方が効率的ですか?そして、どうですか:
pow(x,3)
対
x*x*x // etc?
回答:
私は間のパフォーマンスの違いをテストした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;
}
std::pow
、を使用しない限り、実際には8 * loops回(指数> 2の場合)を呼び出します-fno-math-errno
。その後、私が思ったように、pow呼び出しをループから外すことができます。私はerrnoがグローバルであるため、スレッドの安全性により、powを呼び出してerrnoを複数回設定する必要がある可能性があります... pow呼び出しは-O3
..(- ffast-math、ループの外でも合計8を実行します。)
pow
ています。ループから外れた呼び出しにインライン化されているため、そこに大きな欠陥があります。また、すべてのテストが同じ時間で実行されるため、FP追加のレイテンシを主にテストしているようです。あなたはtest5
よりも遅いと予想しますtest1
が、そうではありません。複数のアキュムレータを使用すると、依存関係チェーンが分割され、レイテンシが隠されます。
pow
、刻々と変化する値に適用することにより、ベンチマークを修正してみます(繰り返される捕虜式が持ち上げられるのを防ぐため)。
それは間違った種類の質問です。正しい質問は次のとおりです。「人間のコードを読む人にとって、どちらが理解しやすいのですか?」
速度が重要な場合は(後で)、尋ねずに測定してください。(そしてその前に、これを最適化することで実際に顕著な違いが生じるかどうかを測定します。)それまでは、最も読みやすいようにコードを記述してください。
編集
だけでは(それが既にされている必要がありますが)これを明確にする:画期的なスピードアップは、通常のようなものから来る、より良いアルゴリズムを使用して、データの局所性を向上させること、動的メモリの使用量の削減、結果を事前に計算し、など彼らはめったにから来ます単一の関数呼び出しをマイクロ最適化し、それらを行う場所は非常に少ない場所です。これは、慎重な(そして時間のかかる)プロファイリングによってのみ検出されますが、非常に直感的でないことで高速化することはできません。もの(挿入するようなnoop
ステートメント)、および1つのプラットフォームの最適化とは、別のプラットフォームの悲観化となる場合があります(環境を完全に把握していないため、質問するのではなく測定する必要があるのはこのためです)。
もう一度強調しておきます。そのようなことが重要ないくつかのアプリケーションでさえ、それらは使用されるほとんどの場所では重要ではなく、コードを見ても重要な場所を見つけることはほとんどありません。最初にホットスポットを特定する必要があります。それ以外の場合、コードの最適化は時間の無駄に過ぎないためです。
1つの操作(ある値の2乗の計算など)がアプリケーションの実行時間の10%を占める場合でも(IMEは非常にまれです)、最適化してもその操作に必要な時間の50%を節約できます(IMEはさらに、はるかにまれですが)、アプリケーションの所要時間を5%短縮しました。
ユーザーはそれに気づくためにストップウォッチが必要になります。(ほとんどの場合、20%未満の高速化はほとんどのユーザーに気付かれずに済むと思います。それが4つのそのようなスポットを見つける必要があります。)
x*x
または一般的なケースを処理する必要x*x*x
があるので、よりも高速になりますがpow
、pow
x*x
固有のものです。また、関数呼び出しなどを省略できます。
ただし、このようにマイクロ最適化している場合は、プロファイラーを入手して、本格的なプロファイリングを行う必要があります。圧倒的な確率は、この2つの違いに気付かないことです。
x*x*x
2倍std::pow(double base, int exponent)
になっただけで、統計的に意味のあるパフォーマンスの違いがわかりません。
また、パフォーマンスの問題についても疑問に思っていたところ、@ 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()を実行する場合、これは間違いなく覚えておく価値があります...
最も効率的な方法は、乗算の指数関数的な増加を考慮することです。このコードで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;
}
指数が一定で小さい場合は、それを拡張して乗算の数を最小限に抑えます。(例えば、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
てプロファイラーに(当然のことながら)時間を浪費していることを通知させる-インテリジェントに行うことで、このステップを省略できます。)
私は同様の問題で忙しくしており、その結果にはかなり困惑しています。私は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
x
整数や浮動小数点は?