(更新:clang ++とg ++を使用したタイミング実験については最後に説明します。また、簡単にするためcomp_rt
に、質問で正確な本体を使用して、関数の本体を書き直す必要なく完全に最適化できることを示しています。)
はい、これは可能です。しかし、g++ は、あなたが気付かないうちに、とにかく多くのことを行っているようです。最後の実験を参照してください。ただし、clang++ を使用すると、ランタイム バージョンが遅くなることが実際にわかります。
以下のプログラムでは、 を除くすべてのパラメータX
がテンプレート引数として渡されます。したがって、comp_rt
使用するパラメーターの組み合わせごとに異なるテンプレート関数が作成されます。が大きい場合、これによりバイナリが大きくなる可能性がありますL
。
私の扱い方はD[i]==0
、最初はわかりにくいかもしれません。の中に入れましたenable_if
。comp_tpl
ここには 2 つの定義がありD[i]==0
ますD[i]==1
。正直なところ、これはおそらく不要です。単一のcomp_rt
関数テンプレート内で関数の元の本体を使用しただけでも、コードは最適にコンパイルされるのではないかと思います。 (私はこの複雑さを取り除きました)。
関数内に次のような行を含めました。
using confirm_Ei_is_known_at_compile_time = array<char,E[i]>;
E[i]
これにより、コンパイル時に がコンパイラに認識されていることが確認されます。これは typedef と同等であり、 の要素数はarray
コンパイル時にわかっている必要があります。たとえば、 のX[i]
代わりにを使用しようとすると、コンパイラはコードを拒否しますE[i]
。array
注: この行は何もしません。コンパイル時のサニティ チェックです。
最後に、それE[i]
がコンパイル時にわかっている場合、コンパイラはループをアンロールできます (賢明な判断で、それによって速度が向上すると思われる場合)。必ずすべての最適化をオンにしてください。gcc にはオプションがあります-funroll-all-loops
。
関連するパラメーターをテンプレート パラメーターとして渡すことにより、コンパイラはより多くの最適化を行うことができます。しかし、それを選択するかどうかはわかりません! 実験が必要です。
これが、タイミング実験に使用した完全なプログラムです。
#include<array>
#include<iostream>
using namespace std;
/*
* L is a positive integer
* D is vector of booleans of length L
* E is a vector of ints [0,L) of length L
* i will be in [0,L) also, therefore it is small enough that we can
* treat it as if it's known at compile time also
*
* The only thing that is *not* known at compile time is:
* X is a vector of ints of length L
*
* Therefore, our goal is something like:
*
* template<int L, int i, int D[L], int E[L]>
* int compute(int X[L]);
*/
template<int L, int i, const bool (&D)[L], const int (&E)[L]> // arrays passed, by reference, at compile-time
typename enable_if< D[i]==0 , int> :: type
comp_tpl(int (&)[L]) {
return 10;
}
template<int L, int i, const bool (&D)[L], const int (&E)[L]> // arrays passed, by reference, at compile-time
typename enable_if< D[i]==1 , int> :: type
comp_tpl(int (&X)[L]) {
int v = 0;
//using confirm_Ei_is_known_at_compile_time = array<char,E[i]>;
for (int j = 0; j < E[i]; ++j) // E[i] known at compile-time
v += X[j] * (j + 1); // X[j] known at run-time
return v;
}
template<int L, int i, const bool (&D)[L], const int (&E)[L]> // arrays passed, by reference, at compile-time
int
comp_tpl_simple(int (&X)[L]) {
if (D[i] == 0) // D[i] known at compile-time
return 10;
int v = 0;
using confirm_Ei_is_known_at_compile_time = array<char,E[i]>;
for (int j = 0; j < E[i]; ++j) // E[i] known at compile-time
v += X[j] * (j + 1); // X[j] known at run-time
return v;
}
template<int L> // arrays passed, by reference, at compile-time
int
comp_rt(int i, const bool (&D)[L], const int (&E)[L], int (&X)[L]) {
if (D[i] == 0) // D[i] known at compile-time
return 10;
int v = 0;
for (int j = 0; j < E[i]; ++j) // E[i] known at compile-time
v += X[j] * (j + 1); // X[j] known at run-time
return v;
}
constexpr int L = 5;
extern constexpr bool D[L] {0, 1, 1, 0, 1}; // Values in {0, 1}
extern constexpr int E[L] {1, 0, 3, 2, 4}; // Values in [0, L-1]
void change_X_arbitrarily(int (&X)[L]) {
for(int j=0; j<L; ++j)
++X[j];
}
int main() {
int X[L] {1, 3, 5, 7, 9}; // Any integer
#ifdef USE_RUNTIME
#define comp(L,i,D,E,X) comp_rt<L>(i,D,E,X)
#endif
#ifdef USE_TEMPLATE
#define comp(L,i,D,E,X) comp_tpl_simple<L,i,D,E>(X)
#endif
int total=0;
for(int outer_reps=0; outer_reps<10000; ++outer_reps) {
for(int inner_reps=0; inner_reps<100000; ++inner_reps) {
total += comp(L,0,D,E,X);
total += comp(L,1,D,E,X);
total += comp(L,2,D,E,X);
total += comp(L,3,D,E,X);
total += comp(L,4,D,E,X);
}
change_X_arbitrarily(X);
}
cout << total << endl; // should be 39798784
}
#define
使用する関数を選択するために a を使用する方法に注意してください。私はコンパイルして実行します:
$ clang++ SO.cpp -std=gnu++0x -O3 -DUSE_TEMPLATE -o SO && time -p ./SO
39798784 // the total value from all the calls, as a check
real 0.00
user 0.00
sys 0.00
1,000,000,000 回の計算にかかる時間は 0 秒です。ただし、ランタイム バージョンは 2.7 秒かかります
$ clang++ SO.cpp -std=gnu++0x -O3 -DUSE_RUNTIME -o SO && time -p ./SO
39798784 // the total value from all the calls, as a check
real 2.70
user 2.68
sys 0.00
そこでclang3.3を使用し-O3
ました。
g++ 4.8.2 を使用すると、-O3 で未定義の動作に関する警告が表示されますが、奇妙なことに、ランタイム バージョンまたはテンプレート バージョンのいずれかでランタイムが 0 秒です! おそらく g++ は、「ランタイム」モードであっても、コンパイル時のトリックを有効にしています。ここでの教訓は、コンパイラは私たちよりも最適化について多くのことを知っているということです!
とにかく、フォールバックするとg++-4.8.2 -O2
、実行時間はどちらの場合も 6.8 秒です。かなり奇妙です!さらに追加すると、O
速度が低下することがあります。
説明:この場合、X
実際にはコンパイル時に認識されます。これはこのコードのローカル変数であり、決定論的に更新されるため、コンパイラはそれを完全に予測でき、コンパイル時に答えを計算します! g++ がこれを行っているようです (非常に印象的です!)。したがって、私の最新の実験では、グローバル変数のX
外main
はcomp_tpl
一貫して現在よりもはるかに高速ですcomp_rt
。