现代的CPU中,执行运算的function unit 大多是pipe line 的。也就是在上一条指令没有执行完成前,就可以执行下一条指令了。
但是之前我们实现的代码并没有利用到这点,因为运算结果保存在一个变量中。这样在计算时,必须等待上一次执行的结果,来计算下一次的值,而不能利用pipe line实现同步运行了。
对于某些运算, 如加法,我们可以把将要计算的元素 分成几组,分别计算每个组的结果,最后来算组的结和 。
1 /* Unroll loop by 2, 2-way parallelism */
2 void combine6(vec_ptr v, data_t *dest)
3 {
4 int length = vec_length(v);
5 int limit = length-1;
6 data_t *data = get_vec_start(v);
7 data_t x0 = IDENT;
8 data_t x1 = IDENT;
9 int i;
10
11 /* Combine 2 elements at a time */
12 for (i = 0; i < limit; i+=2) {
13 x0 = x0 OPER data[i];
14 x1 = x1 OPER data[i+1];
15 }
16
17 /* Finish any remaining elements */
18 for (; i < length; i++) {
19 x0 = x0 OPER data[i];
20 }
21 *dest = x0 OPER x1;
22 }
上面这段代码,就是将待计算的值分成了奇数和偶数两个组,分别计算。 最后相加各个组的结果。
相应的汇编代码是:
load (%eax, %edx.0, 4) -> t.1a
imull t.1a, %ecx.0 -> %ecx .1
load 4(%eax, %edx.0, 4) -> t.1b
imull t.1b, %ebx.0 -> %ebx .1
addl $2, %edx.0 -> %edx.1
cmpl %esi, %edx.1 -> cc.1
jl-taken cc.1
从上面的代码中看到,分别使用了两个寄存器 %ecx, %ebx来保存 奇数组 和 偶数组的结果 。 这样就提高了运算性能。
注意事项:
1. 对于整数运算,就算结果溢出,计算顺序不会影响结果。
对于浮点数,计算顺序会影响结果。 比如,奇数组都是正很大的数,而偶数组都是负很大的数。实际值应该是0左右,但如果采用了上面的方法
得出的结果就是无穷。
2. 并不是分的组越多越好
因为通过分组来提高性能,是基于寄存器的。 如果分的组超过了寄存器的个数,就需要将中间值保存到堆栈。 这样反而会降低性能。