CSAPP 第五章-优化程序性能

  • step 1. 消除不必要的工作

  • step 2. 利用指令级并行能力,同时执行多条指令

5.1 优化编译器的能力和局限性

大多数编译器向用户提供了一些对它们所使用的优化的控制

GCC中,-Og是让GCC使用一组基本的优化,-O1|-O2|-O3使用更大量的优化

编译器必须很小心的对程序只使用安全的优化

void twiddle1(long *xp, long *yp){
	*xp += *yp;
	*xp += *yp;
}

void twiddle2(long *xp, long *yp){
	*xp += 2 * (*yp)
}

二者大多数情况下行为相同,看上去 twiddle2 效率更高,但当 * xp 与 * yp值相同时,二者所得到的结果完全不同。

对于编译器,其不知 twiddle1 会如何调用,因此必须假设存在 XP 和 YP相等的情况,所以不能优化

这种情况称为 内存别名使用(memory aliasing)

5.2 表示程序性能

**每元素的周期数(Cycles Per Elements)**作为一种表示程序性能并指导我们改进代码的方法。

处理器活动顺序由时钟控制,时钟提供某个频率的规律信号,通常用GHZ,十亿周期每秒来表示。

用时钟周期来表示,度量值表示执行多少条指令。

5.3 程序示例

整体结构:

#define IDENT 0
#define OP +

/*
#define IDENT 1
#define OP *
*/

//different Operator has different status

typedef struct{
	long len;
	data_t *data;
} vec_rec, *vec_ptr

/* create vector of specified length*/

vec_ptr new_vec(long len){
	...
	/* ignore the specify codes*/
	/* it's about creating new vec_rec */
}

int get_vec_element(vec_ptr v, long index, data_t *dest){
	if( index < 0 || index > v->len)
		return 0;
	* dest = v->data[index];
	* return 1;
}

long vec_length(vec_ptr v){
	return v->len;
}

接下来,利用上述整体结构,进行代码的一系列变化,并进行性能刨析。

在具有 Intel Core i7 Haswell 的处理机上测量函数的CPE性能

计算向量元素的乘积:

void combine1(vec_ptr v, data_t *dest){
	long i;
	
	*dest = IDENT;
	for(i = 0; i < vec_length(v), i++){
		data_t val;
		get_vec_element(v,i,&val);
		*dest = *dest OP val;
	}
}

性能测试:

函数 方法 整数 浮点数
combine1 未优化 22.68 19.98
combine1 -O1 10.12 10.17

5.4 消除循环的低效率

vec_length 的值是固定的,循环中不需要每次都调用:

void combine2(vec_ptr v, data_t *dest){
	long i;
	long length = vec_length(v);
	
	*dest = IDENT;
	for(int i = 0; i < length; ++i){
		dest val;
		get_vec_element(v, i, &val);
		*dest = *dest OP val;
	}
}

性能测试:

函数 方法 整数 浮点数
combine1 -O1 10.12 10.17
combine2 移动vec_length 7.02 9.02

这个优化是一类常见的优化例子,称为代码移动(code motion)。包括要执行多次,但计算结果不会改变的计算。

5.5 减少过程调用

每次循环迭代都会调用 get_vec_element来获取下一向量元素,对每个向量引用,都要进行边界检查。

void combine3(vec_ptr v, data_t *dest){
	long i;
	long length = vec_length(v);
	
	*dest = IDENT;
	data_t *element = v->data;
	
	for(int i = 0; i < length; ++i){
		*dest = *dest OP *element[i];
	}
}

没有使用函数调用获取每个向量元素,而是直接访问数组。

性能测试:

函数 方法 整数 浮点数
combine2 移动vec_length 7.02 9.02
combine3 直接数据访问 7.17 9.02

令人吃惊的是,性能没有明显提升,而且整体性能还略有下降。

显然,内循环中其他的操作形成了瓶颈,限制性能超过调用 get_vec_element

5.6 消除不必要的内存引用

通过检查编译combine3的内循环产生的汇编代码:

inner loop of combine3, data_t = double, OP=*
dest in %rbx, data+i in %rdx, data+length in %rax
.L17:
vmovsd (%rbx), %xmm0
vmulsd (%rdx), %xmm0, %xmm0
vmovsd %xmm0, (%rbx)
addq $8, %rdx
cmpq %rax, %rdx
jne .L17

每次迭代时,累积变量的数值都要从内存读出再写入内存,这样的读写很浪费。

void combine4(vec_ptr v, data_t *dest){
	long i;
	long length = vec_length(v);
	
	data_t acc = IDENT
	data_t *element = v->data;
	
	for(int i = 0; i < length; ++i){
		acc = acc OP *element[i];
	}
}

此时汇编代码

inner loop of combine3, data_t = double, OP=*
data+i in %rdx, data+length in %rax
.L25:
vmulsd (%rdx), %xmm0, %xmm0
addq $8, %rdx
cmpq %rax, %rdx
jne .L25

性能测试:

函数 方法 整数 浮点数
combine3 直接数据访问 7.17 9.02
combine4 累计在临时变量中 1.27 3.01

使用了这些变换,性能相当大的提高了,现在我们看看是什么因素制约着代码的性能,以及如何进一步提高

接下来的5.7 - 5.12 主要为优化程序使其达到硬件可到达界限

5.13 - 5.14 为Linux操作系统提供的程序刨析工具

涉及的硬件知识较多 这里如果展开还需将第三章 第四章的内容融入

会慢慢将其补全

展开阅读全文
©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值