利用模板元编程实现解循环优化
2008-08-09 19:26:08 来源:WEB开发网循环点积计算: 编译器甚至连inline也不做了,进行正式的函数调用。
模板元点击计算:依然就地展开。
dim=32:
循环点积计算:不展开,调用
模板元点积计算:
比较有趣的是,编译器也没有将函数就地展开,而是分成3步。就地计算前9个乘法然后与DotProduct<23,int>的返回值相加。DP<23>也没有就地计算全部,而是计算前11个,然后与DP<12>的返回值相加。
3步计算当中都没有循环。
值得注意的是,循环点积计算函数也没有很老实的按照源代码的方式进行计算,而是进行了不完全的循环展开,类似于如下代码:
template< typename T>
T dot_product(int DIM,const T v1[],const T v2[]) {
const int step = complierKnow;
T results[step] = {0};
int i=0;
for (;i+step-1<DIM;i+=step) {
results[0] += v1[i]*v2[i];
results[1] += v1[i+1]*v2[i+1];
...
results[step-1] += v1[i+step-1]*v2[i+step-1];
}
for (;i<DIM;++i)
results[0] += v1[i]*v2[i];
return results[0]+result[1]+...result[step-1];
}
DIM和step似乎没有什么固定的规律。
(详细代码见sample2,取不同的dim值即可。)
验证3:
sample2中,编译器对被计算的向量的上下文依然知情:v1,v2在main函数的栈中。
sample3做了小小的改动:dot_product_loop和dot_product_unloop将无从得知传递给它们的向量在何处。(当然,在main函数中,仍然使用函数指针来调用这2个函数,使得编译器不做inline)
sample3得到的结果和sample2基本相同,只是对向量寻址由esp+v1_offset,esp+v2_offset变为[esp+4],[esp+8]
(详细代码见sampl3)
验证4:
在sample3的基础上,通过控制台输入读取dim,使得编译对其不知情。
当然,在这种情况下,是无法使用模板元解循环的,因为它要求dim是编译时常量。
在这种情况下,循环点积计算终于被编译器翻译成真正的“循环”了。
总结:
循环版本有更大的灵活性:在dim为编译时常量时,编译器会根据其大小,进行完全解循环或部分解循环。同时也支持dim为运行时的值。
模板元版本必须使用编译时常量作为dim,而且总是完全解开循环。
循环版本与模板元版本最终都会使用相同次数的乘法与运算,区别在于乘法指令和跳转指令的数目。
模板元版本在dim较大的时候,可能会使用跳转指令,将部分工作交给更小维度的点积计算。但是乘法指令数目与维数一样。
循环版本可能会使用更多的跳转指令,使得乘法指令数目大大减少。
多出的跳转指令会占用运行时间,但也能减少目标代码体积。权衡应该使用那一种版本与权衡某个函数是否应该inline十分相似。
书中17章最后部分也提醒读者,不计后果的解循环并不总是能优化运行时间。
借用大师的观点来结束本文 —— 优化的第一原则是:不要优化。优化的第二原则(仅适用于专家)是:还是不要优化。再三测试,而后优化。
《C++编程规范》 第8条
更多精彩
赞助商链接