これらの2つの方法のうち、Cでより効率的なものは何ですか?そしてどうですか:
pow(x,3)
vs.
x*x*x // etc?
このコードを使用して、小さなi
のx*x*...
とpow(x,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
すべてのパウ計算の結果を蓄積して、コンパイラーがそれを最適化しないようにします。
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上にあります。 -o2最適化でgcc 4.4.1を使用してコンパイルしました。
そのため、Cでは、pow(x, 3)
オーバーロードがないため、はいx*x*x
はpow(double, int)
より高速です。 C++では、ほぼ同じになります。 (私のテストの方法論が正しいと仮定します。)
これは、Markmによるコメントへの応答です。
using namespace std
ディレクティブが発行された場合でも、pow
の2番目のパラメーターがint
である場合、<cmath>
のstd::pow(double, int)
の代わりに<math.h>
の::pow(double, double)
オーバーロードが呼び出されます。
このテストコードは、その動作を確認します。
#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;
}
それは間違った種類の質問です。正しい質問は、「私のコードを読む人にとって理解しやすいのはどれですか?」です。
速度が重要な場合(後で)、尋ねるのではなく測定します。 (そして、その前に、これを最適化することが実際に顕著な違いを生むかどうかを測定します。)それまでは、読みやすいようにコードを書きます。
Edit
これを明確にするだけです(既にそうなっているはずですが):画期的な高速化は、通常より良いアルゴリズムを使用して、- データの局所性の改善、動的メモリの使用の削減、- 事前計算結果など。これらは、単一関数呼び出しのマイクロ最適化から得られることはほとんどありません、およびその場合、ごく少数の場所で行われます。これはcareful(そして時間がかかる)profiling、多くの場合、決して高速化できない非常に直感的でないこと(noop
ステートメントの挿入など)を行うことで、あるプラットフォームの最適化とは別のプラットフォームの悲観化になることもあります環境を知っている/持っている)。
もう一度強調しましょう:そのようなことが重要ないくつかのアプリケーションでも、使用されるほとんどの場所で重要ではありませんveryコードを見ることで重要な場所を見つけることができますあなたは本当に最初にホットスポットを特定する必要がありますコードの最適化はただ時間の無駄です。
単一の操作(値の2乗の計算など)がかかる場合でもアプリケーションの実行時間の10%(IMEは非常にまれです)、そして、最適化によって50%の時間がその操作に必要な場合(IMEはさらにはるかにまれです)、アプリケーションを作成しましたtake 5%少ない時間のみ。
ユーザーはそれに気づくためにストップウォッチが必要になります。 (ほとんどの場合、20%のスピードアップ以下はほとんどのユーザーに気付かれないと思います。thatはそのような4つのスポットを見つける必要があることです。)
x*x
は特定のケースですが、pow
は一般的なケースを処理する必要があるため、x*x*x
またはx*x
はpow
よりも高速です。また、関数呼び出しなどを省略することができます。
ただし、このように微最適化されている場合は、プロファイラーを入手して、本格的なプロファイリングを行う必要があります。圧倒的な確率は、この2つの違いに気付かないことです。
パフォーマンスの問題についても疑問に思っていましたが、@ 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)ケース)。いずれの場合も、手動拡張はより優れたパフォーマンスを発揮しましたが、特にPower 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
where y=x*x
です。そしてx^5
はy*y*x
where y=x*x
です。など)。定数整数の指数については、最適化された形式を書き出すだけです。指数が小さい場合、これはコードがプロファイルされているかどうかに関係なく実行される標準的な最適化です。最適化されたフォームは、非常に大きな割合のケースでより高速になり、基本的に常に実行する価値があります。
(Visual C++を使用している場合、std::pow(float,int)
は、ほのめかしている最適化を実行します。これにより、操作のシーケンスは指数のビットパターンに関連します。そのため、まだ手作業で行う価値があります。)
[編集]ところで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
では不可能です。