(更新:最后讨论了 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
在编译时必须知道 an 中的元素数量。例如,如果您尝试使用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
}
请注意我如何使用 a#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
。