(更新:最后讨论了 clang++ 和 g++ 的计时实验。另外,为简单起见,我在问题中使用了 comp_rt 的确切正文,证明它可以完全优化而无需我们重写函数体。)
是的,这可以做到。但是,g++ 似乎在你没有意识到的情况下为你做了很多这样的事情,请参阅最后的实验。但是,使用 clang++,您确实可以看到运行时版本变慢了。
在下面的程序中,除了X 之外的所有参数都作为模板参数传递。因此,将为您使用的每种参数组合构建不同的comp_rt 模板函数。
如果L 很大,这可能会导致您的二进制文件变大。
我传递D[i]==0 的方式一开始可能很难理解。
我把它放在enable_if 中。这里comp_tpl的定义有两种,一种在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 来选择要使用的函数。我编译运行:
$ 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 次需要零秒!但运行时版本需要 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 未定义行为的警告,但奇怪的是,无论是运行时版本还是模板版本,运行时间都是零秒!也许 g++ 为我们启用了编译时技巧,即使在“运行时”模式下也是如此。这里的教训是,编译器确实比我们更了解优化!
无论如何,如果我退回到g++-4.8.2 -O2,那么无论哪种情况,运行时间都是 6.8 秒。很奇怪!有时添加更多 O 会减慢速度!
解释: 在这种情况下,X 实际上在编译时是已知的。它是这段代码中的一个局部变量,并且确定性地更新,因此编译器能够完全预测它并在编译时计算答案!看来 g++ 正在这样做(非常令人印象深刻!)。因此,在我最近的实验中,我将X 移到了main 之外,使其成为一个全局变量,现在优化行为“如预期”。 comp_tpl 现在始终比 comp_rt 快得多。