提升C++代码效率的几种方法二
(1)循环展开提升代码效率
循环展开是一种程序变换,通过增加每次迭代计算的元素的数量,减少循环的迭代次数。它从两个方面改进程序的性能:
1、它减少了不直接有助于程序结果的操作的数量,例如循环索引计算和条件分支;
2、它提供了一些方法,可以进一步变化代码,减少整个计算中关键路径上的操作数量。
//求数组元素的累积,放在 dest 所指的内存单元中
void combine(vector<double> v, double *dest)
{
int i;
int length = v.size();
double acc;
for(int i = 0; i < length - 1; i += 2)
acc = (acc * v[i]) * v[i+1];
for(; i < length; i++) //处理剩下的元素
acc = acc * v[i];
*dest = acc;
}
上述代码一次求取两个元素的乘积,然后存放在局部变量 acc 中,最后再存到内存单元中。该代码减少了 for 循环中的判断的次数,从而减少了触发处理器预测机制的次数,大大提升了代码的效率。
(2)通过提高代码的并行性提升代码的效率
对于一个可结合和和交换的合并运算来说,比如整数的加法或乘法,我们可以通过将一组合并运算分割成两个或更过的部分,并在最后合并结果来提升性能。
接下来改进上述代码:
//求数组元素的累积,放在 dest 所指的内存单元中
void combine(vector<double> v, double *dest)
{
int i;
int length = v.size();
double acc1, acc2; //分别存放数组中下标为奇数和偶数的元素累积
for(int i = 0; i < length - 1; i += 2)
{
acc1 = acc1 * v[i];
acc2 = acc2 * v[i+1];
}
for(; i < length; i++) //处理剩下的元素
acc = acc * v[i];
*dest = acc1 * acc2;
}
上述代码先分别求取数组中下标为奇数和偶数的元素累积,然后分别存放在两个局部变量 acc1 和 acc2(分别存在寄存器 %xmm0 和 %xmm1)中,这两个寄存器可以并行的执行,互不干扰。不像只循环展开的代码,要等到第一个乘法运算完才能进行第二个乘法。并行代码结合了循环展开和并行两种特征,大大地提高了代码地效率。
(3)重新结合变换
对于(1)中的代码,我们可以进行以下改造:
//求数组元素的累积,放在 dest 所指的内存单元中
void combine(vector<double> v, double *dest)
{
int i;
int length = v.size();
double acc;
for(int i = 0; i < length - 1; i += 2)
acc = acc * (v[i] * v[i+1]); //此处变成了后面两个元素先乘
for(; i < length; i++) //处理剩下的元素
acc = acc * v[i];
*dest = acc;
}
这段代码变成了先求取 v[i] 和 v[i+1] 的乘积,然后再和 acc 相乘,这样就减少了关键路径上的操作,也可以理解为不需要对 acc 进行多次乘运算,该代码只对 acc 做了一次乘运算,而(1)就对 acc 做了2次乘运算。此时效率也是提升了很多。总的来说,重新结合变换能够减少计算中关键路径上操作的数量,通过更好地利用功能单元的流水线能力得到更好的性能。通常,循环展开和并行地累积在多个值中,是提升程序性能的更可靠方法。
由于浮点数的乘法和加法是不可结合的,因此,由于四舍五入或溢出,(2)和(3)的代码产生的结果可能不同。因此,对于浮点数的情况,我们必须评估这些方法可能产生的严重后果。
内容均来自《深入理解计算机系统》这本书,希望对大家编写代码有帮助。