优化程序性能
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
优化程序性能的基本方法
• 高级设计
– 算法、数据结构
• 基本编码原则
– 消除连续重复的函数调用,把计算移动 到循环外 – 消除不必要的存储器引用,引入临时变 量来保存中间结果
• 低级优化
– 尝试各种与数组代码相对的指针形式 – 通过展开循环降低循环开销 – 通过迭代分割利用流水线化的功能单元
提纲
优化效果小结
函数 优化方法 combine1 原始函数 combine1 -O2编译 Combine2 移动vec_length Combine3 直接数据访问 Combine4 累积在临时变量 Combine5 展开 X4 Combine6 并行 X2 优化效果(加速比) 周期数 41.44 31.25 21.15 8.00 3.00 3.00 1.50 27.6X
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
消除循环的低效率
for(int i=0; i< vec_length(v); i++) Void combine2(vec_ptr v, data_t *dest) { int length = vec_length(v); *dest = 0; for(int i=0;i<length;i++) { data_t val; get_vec_element(v, i, &val); *dest = *dest + val; } }
循环分割 (loop splitting)
Pn = ∑ i =0 ai
假设n是偶数 =>
n −1
PEn = ∑ i =0 a2i POn = ∑ i =0 a2i +1 Pn = PEn + POn
n 2−2
n 2−2
循环分割 (loop splitting)
void combine6(vec_ptr v, data_t *dest) { int length = vec_length(v); int limit = length – 1; data_t *data = get_vec_start(v); data_t x0 = 0; data_t x1 = 0; int i; for(i=0;i<limit;i+=2){ x0 = x0 + data[i]; x1 = x1 + data[i+1]; } for( ;i<length;i++) x0 = x0 + data[i]; *dest = x0 + x1; }
Amdahl定律
• 系统原来占用60%时间的部分被 提速了3倍
–Told = 0.4 + 0.6 = 1 –Tnew = 0.4 + 0.6/3 = 0.6 –SpeedUp = Told/Tnew =1/0.6 =1.67
程序示例
void combine1(vec_ptr v, data_t *dest) { *dest = 0; for(int i=0; i < vec_length ( v ) ; i++) { data_t val; get_vec_element(v, i, &val); *dest = *dest + val; } }
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
提高并行性
• 处理器的几个功能单元是流水 线化的 • 可以在前一个操作完成之前开 始一个新的操作
优化程序性能
张 琦 (Qi Zhang) CS, USTC xiaoga@mail.ustc.edu.cn Dec. 2007
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
基本方法
编写高效的程序
• 选择一组最好的算法和数据结 构 • 编写出编译器能够有效优化以 转换成高效可执行程序的源代 码
基本策略
• 在实现和维护的简单性与运行速 度之间做出权衡折衷
• 只运行少数几次的程序
–减少编程工作量
• 需要反复执行的程序
–仔细的优化
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
降低循环开销
• 循环的每次迭代包括的指令
–程序数据操作 –循环开销
减少过程调用
• 动机
–过程调用带来相当大的开销 –过程调用妨碍编译器优化
• 减少过程调用的结果
–提高代码运行速度
(上例中提速3.5X)
–损害一些程序的模块性
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
降低循环开销
• 循环展开的缺点
–当向量长度不能被展开度整除时, 需要另外完成所有剩下的元素 –增加了生成的目标代码的数量
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
消除不必要的存储器引用
Void combine4(vec_ptr v, data_t *dest) { int length = vec_length(v); data_t *data – get_vec_start(v); data_t x = 0; for(int i=0;i<length;i++) { x = x + data[i]; } *dest = x; }
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
减少过程调用
void combine3(vec_prt v, data_t * dest) { int length = vec_length(v); data_t *data = get_vec_start(v); *dest = 0; for(int i=0;i<length;i++) { *dest = *dest + data[i]; } }
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
程序示例
typedef struct { int len; data_t *data; } vec_rec, *vec_ptr; vec_ptr new_vec (int len); int get_vec_element(vec_ptr v, int index, data_t *dest); int vec_length(vec_ptr v);
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
பைடு நூலகம்
确认和消除性能瓶颈
• 优化小的程序 • 优化大规模程序
–收集性能数据 –确认性能瓶颈 –消除性能瓶颈
编译器优化的局限性 – 指针
• 考虑 xp==yp 的情况 *xp += *xp; *xp += *xp; => *xp增加为以前的4倍 *xp += 2 * (*xp); => *xp增加为以前的3倍 • 妨碍优化的因素
编译器必须假设不同的指针可能会 指向存储器中的同一个位置(memory aliasing)
• 计算循环索引,测试循环条件
• 循环展开(loop unrolling)
–在每次迭代中执行更多的数据操 作来减小循环开销的影响
降低循环开销
void combine5(vec_ptr v, data_t *dest) { int length = vec_length(v); int limit = length – 2; data_t *data = get_vec_start(v); data_t x = 0; int i; for(i=0;i<limit;i+=3) x = x + data[i] + data[i+1] + data[i+2]; for(;i < length; i++) x = x + data[i]; *dest = x; }
代码移动 (code motion)
• 识别出要执行多次但是计算结 果不会改变的计算 • 把计算移动到代码前面的、不 会被多次求值的部分
–循环内部 -> 循环外部
练习1
size_t strlen(const char *s); void lower1(char *s) { for(int i=0;i<strlen(s);i++) if(s[i]>=‘A’ && s[i]<=‘z’) s[i] -= (‘A’-’a’); }
对并行的限制
• 寄存器数量
–并行度超过可用寄存器数量 –系统将某些临时值存放到栈中 –性能会急剧下降
• 功能单元的能力
–执行时间(latency)
• 完成一个操作所需要的总周期数
–发射时间(issue time)
• 连续的、独立操作之间的周期数
提纲
• • • • • • • • • • • 优化程序基本方法 优化编译器的能力和局限性 程序示例 消除循环的低效率 减少过程调用 消除不必要的存储器引用 降低循环开销 转换到指针代码 提高并行性 优化效果小结 确认和消除性能瓶颈
转换到指针代码
void combine4p(vec_ptr v, data_t *dest) { int length = vec_length(v); data_t *data = get_vec_start(v); data_t *dend = data+length; data_t x = 0; for(;data<dend;data++) x = x + *data; *dest = x; }
优化编译器的能力和局限性
• 编译器会帮助我们做一些优化
–命令行选项 –O –O2 –O3
• 编译器优化的局限性
– 不能改变正确的程序行为 – 对程序行为、环境的了解有限 – 需要很快完成编译工作
编译器优化的局限性 – 指针
void twiddle1(int *xp, int *yp) { *xp += *yp; *xp += *yp; } //访存6次 void twiddle2(int *xp, int *yp) { *xp += 2 * (*yp); } //访存3次 twiddle1与twiddle2是等价的么?
编译器优化的局限性 – 函数调用
int f ( int ); int func1( int x ) { return f(x) + f(x) + f(x) + f(x); } int func2( int x ) { return 4*f(x); }
编译器优化的局限性 – 函数调用
int counter = 0; int f(int x) { return counter++; } - 改变调用f的次数会改变程序 的行为 - 通常编译器会保持所有的函数 调用不变