优化程序性能
1编写高效程序:合适的数据结构和算法,编译器能够有效优化以转换为高效可执行代码的
源码,对处理量特别大的计算将任务分为多个部分;
程序优化:消除不必要的内容(函数调用,条件测试,存储器引用);
使程序性能最大化:需要一个目标机器的模型,指明如何处理指令,以及各个操作的时序
特性;利用处理器提供的指令级并行能力同时执行多条指令;
降低计算不同部分之间的数据相关,增加并行度,同时执行;
研究程序的编译代码表示,是理解编译器以及产生的代码如何运行的最有效的手段之一;
确认关键路径,决定执行一个循环所需要的时间(至少是一个时间下界);
关键路径是在循环的反复执行过程中形参的数据相关链;
2优化编译器的能力和局限性
(1)制定优化级别;
(2)编译器对程序只使用安全的优化,对于程序可能遇到的所有可能情况,优化后的程序和为优化的版本有一样的行为;(考虑指针指示位置是否相同,考虑运算数值是否相同,考虑存储器别名使用,考虑调用函数时对全局变量的修改);
(3)使用内联函数替换对函数的调用,转换为函数体;
3程序性能表示
CPE每元素的周期数,表示程序性能并指导改进代码的方法;
用时钟周期表示,度量值表示的是指向了多少条指令而不是时钟运行的多快;
4消除循环的低效率
(1)(代码移动)将固定计算移出循环体,比如计算strlen,避免引入这样的渐进低效率;
(2)(减少过程调用)并没有很大提升(操作延迟);
(3)(消除不必要的存储器引用)只在计算结束才存放,避免多次的读写;
5理解现代处理器
(1)优化程序性能,考虑利用处理器的微体系结构;
(2)延迟界限,下一条指令开始前这条指令必须执行结束;
吞吐量界限,刻画了处理器功能单元的原始计算能力,是程序性能的终极限制;
超标量,可以在每个时钟周期执行多个操作而且乱序的;
指令控制单元(ICU),从指令高速缓存中读取指令,并根据指令序列产生一组针对程序
数据的基本操作;
执行单元(EU)执行操作;
(3)分支预测,投机技术,处理器开始取出位于他预测的分支会跳到的地方的指令并译码甚至在确定分支预测是否正确之前就开始执行;
指令译码;
加载存储,存储器读写;
退役单元,当确定下一条指令之后,才可以更改处理器的状态;
寄存器重命名,值可以直接从一个操作直接转发到另一个操作;
6功能单元的性能
(1)延迟,表示完成运算所需要的总时间;
(2)发射时间,表示两个连续的同类型运算之间需要的最小时钟周期数;
很短的发射时间是通过使用流水线实现的;
(3)发射时间表达的常用方法是指明功能单元的最大吞吐量,定义为发射时间的倒数,
延迟界限给出了任何必须按照严格顺序完成合并运算的函数所需要的最小CPE值;
根据功能单元产生结果的最大速率,吞吐量界限给出了CPE的最小界限;
7处理器抽象的数据模型
程序的数据流,展现不同操作之间的数据相关是如何限制他们的执行顺序的;
关键路径,是执行一组机器指令所需时钟周期数的一个下界;
8循环展开
减少了不直接有助于程序结构的操作的数量;
提供了一些方法进一步变化代码减少整个计算中关键路径上的操作数量;
循环展开,对浮点运算没有改进,因为每天mulss指令被翻译成两个操作:从存储器中加载一个数组元素,把这个值乘以已有的累积值;重新排列简化抽象,关键路径是n个mul操作,迭代次数减半,但每次迭代中还是有两个顺序的乘法操作;
这个关键路径是循环没有展开代码的性能制约因素;
9提高并行性
(1)多个累积变量,两路并行(奇偶性);
(2)重新结合变换(结合律,括号放置);
重新结合变换能够减少计算中关键路径上操作的数量,通过更好地利用功能单元的流水线能力得到更好的性能,大多数编译器不会尝试对浮点运算做重新结合,因为这些运算不保证是可结合的,当前的GCC版本会对整数运算执行重新结合,但是不是总有好的效果,通常,我们发现循环展开和并行地累积在多个值中,是提高程序性能的更可靠的方法。
(3)SSE达到更高并行度;
10限制因素
寄存器溢出;
分支预测和预测错误处罚;
(1)不要过分关心可预测的分支
(2)书写适合条件传送实现的代码
先计算一个条件操作的两种结果,然后再根据条件是否满足从而选取一个。只有在一些受限制的情况下,这种策略才可行,但是如果可行,就可以用一条简单的条件传送指令来实现它。条件传送指令更好地匹配了现代处理器的性能特征;
基于条件数据传送的代码比基于条件控制转移的代码性能好(流水线);
避免进行分支预测,根据分支计算结果,而是将两种结果都计算出来,再赋值;
命令式(判断),功能式(赋值);
11理解存储器性能
大数据加载和存储;读写相关;相关链,关键路径;
12性能提高技术
(1)高级设计:避免使用会渐进的产生糟糕性能的算法或编码技术(代码移动);
(2)基本编码原则:
避免限制优化的因素(不安全因素);
消除连续的函数调用(减少调用开销);
消除不必要的存储器引用(避免每一次的读写操作);
(3)低级优化
循环展开,降低开销,使得进一步优化成为可能(循环展开多路并行);
通过使用例如多个累积变量和重新结合等技术,找到方法提高指令集并行(并行);
用功能的风格重写条件操作,使得编译采用条件数据传送(条件数据传送,避免条件选择);
(4)检查代码确保没有引入错误(基本要求);
13确认和消除性能瓶颈
程序剖析,确定程序各部分需要的时间;
使用剖析程序指导优化;
14Amdahl定律
要想大幅度提高整个系统的速度,必须提高整个系统很大一部分的速度;
15优化程序性能
保存在同一个数据cache中避免读取多次
空间局部性
寄存器中取数,不要有太多参数
暂时用不上的变量不要初始化
避免重复计算,计算保存直接使用
条件判断语句顺序调整
减少函数调用层次
优化需确保没有改变结果的正确性
多重循环,将大循环放在外面
在循环中不要使用try catch
循环内不要创建大量的临时变量
使用局部变量
16程序优化方式
combine1,使用抽象的-O1方法优化
combine2,移动vec_length至循环外面
combine3,直接数据访问,避免过程调用;
combine4,累积在临时变量中;无展开
combine5,展开两次,三次;
combine6,展开2次,二路并行
combine7,展开2次,重新结合
17问题思考方式
发现提出问题:工具使用,检测程序性能(如何结合底层写出高效应用程序)
定义分析问题:找到问题位置
解决问题:代码优化,逻辑判断(处理器结构,高性能程序设计)
文档报告(总结)
具体事例:根据以上原则优化
写应用程序时怎样才能更有效的结合到底层的架构
指针,考虑到是否有存储器别名使用,是否指向同一位置
使用寄存器而非存储器,保存在临时变量中而不是每一次读取
-------->存储器访问需要更多周期,速度慢
循环下标使用 --------->空间局部性
指令执行寻找关键路径,结合判断 ---------->指令执行有所差异,找到关键路径
指令并行,变量重新结合 --------->考虑变量存放
书写易于被翻译为条件数据传送的代码 -------->减少分支预测失败
数据加载和存储
编写代码时注意一些方面
编译器设计