中午装Server 2008, 顺便找来CS:APP看了一会。以后弄创新杯的事情,中间抽空看一看吧。
5.1-5.7 描述了一个内容,即通过代码优化来不断减少CPE(cycles per element, 每元素的周期数)度量
首先要理解CPE
例如 1.4GHZ表示CPU频率 1400兆赫兹,而一个2GHZ的CPU时钟周期为0.5纳秒。
详细的解释看这里:http://www.seenthewind.com/?p=1088
下面两个程序
void vsum1(int n)
{
int i;
for (i = 0; i < n; i++)
c[i] = a[i] + b[i];
}
void vsum2(int n)
{
int i;
for (i = 0; i < n; i+=2) {
c[i] = a[i] + b[i]
c[i+1] = a[i+1] + b[i+1]
}
}
其中,vsum1大约消耗 80+4.0n,而vsum2大约消耗83.5+3.5n。 即 80~84的常数周期加上 3.5或者4.0的线性因子。
而这时候,我们我们的CPE定义为,vsum1为 4.0, vsum2为3.5.
接下来谈一谈代码优化。
1. 消除循环的低效率。
for (i = 0; i < vec_length(v); i++){
// ....
}
将vec_length(v)移出循环式,改为常量,我们一般成为代码移动(Code motion)。可以降低10~20的CPE.
也许你会觉得并不太明显,但是如果是
for (i=0; i < serlen(s); i++) {
// ....
}
这样的话,在长度为262144的字符串时,整整需要3.1分钟完成对字符串的大小写反转,而使用常数只需要0.006秒。
2. 减少调用过程
如果出现在循环中调用检查是否越界的函数。如:
for (i = 0; i < length; i++) {
//....
get_vec_element(v, i, &val); // 获得v向量中第i位元素,存入val
}
可以改为
data_t *data = get_vec_start(v);
for (i = 0; i < length; i++) {
//....
*dest = *dest OPER data[i;]
}
虽然看起来会有些损害模块性和抽象性,但是改进最高可以达到3.5X(即3.5倍,X表示相对性能 = Told/Tnew)
3. 消除不必要的寄存器引用。
开启 -o2开关时,编译器会主动帮助你优化代码,但是大部分时候他会严格保证程序的正确执行,包括某些特殊情况
(比如你想让两个指针指向的值相加,但有时你传入了相同的指针)。
这种时候我们需要稍微研究机器码,分析不必要的寄存器引用,可以优化代码。
例如
movl (%edi), %eax
imull (%ecx, %edx, 4), %eax
movl %eax (%edi)
incl %edx
.....
截取自以下代码的一部分:
for (i = 0; i < length; i++) {
// ....
*dest = *dest OPER data[i];
}
汇编指令1,3对存放在dest中的值频繁读写,看上去是种浪费,所以我们改为:
data_t x = IDENT;
for (i = 0; i < length; i++) {
// ....
x = x OPER data[i];
}
*dest = x;
得到的汇编码:
imull (%eax, %edx, 4), %ecx
incl %edx
这样的好处在 data_t 类型为浮点数的时候尤其明显。CPE下降了100~120。
将会在下一小结继续讨论这个问题。