强者恒强:x86高性能编程笺注之循环(上)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

强者恒强:x86高性能编程笺注之循环(上)
读者须知:《强者恒强:x86高性能编程笺注》该系列文章将分享x86高性能开发方面的实践和思考。

主要容目录如下,欢迎各位业界与我们讨论交流相关话题。

1.什么是性能
2.流水线
3.分支
4.循环
5.缓存
6.预取
7.大页
8.锁
9.RCU
10.无锁
11.SIMD指令
循环一般是程序中计算最密集的代码段,也最容易成为性能的热点。

在熟悉了CPU的流水线之后,有很多很多技巧可以提升执行循环的效率,但程序性能的优化如果仅仅是“堆砌”技巧,那么想必也不会吸引如你这般聪慧的读者。

就如同BUG总是会出现在自己认为一定没问题的地方一样,一些被奉为“万金油”式的优化手段也可能正是性能下降的本因。

对于自己写出来的代码,无论是优化性能还是单纯的DEBUG,“都像是在看一场情节跌宕的犯罪电影,在这里面,你既是侦探,同时也是凶手”。

很少有人会愿意将自己“绳之以法”,因此也只有很少
发展过程中,都积累了深厚的循环优化处理方法。

如果仅仅将编译器认为是缺乏头脑的“执行者”,那么在性能优化的路上,将会失去一个重要的伙伴。

为了说明如何去“配合”而非“驾驭”编译器,本文也将在介绍理论的基础上,更加侧重实践的经验,以测试例的方式摸清编译器的脾气。

数据依赖
单纯对两个变量写入数据,并不产生数据依赖。

无论是这两条语句执行的先后顺序如何,或并行执行,都不影响程序的最终结果。

数据依赖往往产生于对同一个变量的读写之间:
以上两组代码虽然最终结果完全相同,但在计算机看来却是完全不同的两行代
式:
12.read-after-write:写入的变量后续将被读取
13.write-after-read:读取的变量后续将被写入
14.write-after-write:写入的变量后续被重新写入
以上三种依赖类型又可以分别叫做”flow dependence
“antidependence”和”output dependence”
举一个简单的例子:
中没有被同时读写,但在不同的循环迭代之间存在对同一个元素的读写操作。


Loop Distribution and Fusion
从这一节开始会陆续介绍几种常见的循环优化技巧。

但如前所述,理论与实践相结合才能真正发挥人的主观能动性,并激活人与客观世界的奖励反馈机制。

所以针对每一种技巧,除了讲解原理和举个栗子之外,还会给出真实的编译结果以及性能测试结果以便读者揣摩玩味。

Theory
Loop distribution(也叫fission)是指将原本的循环体拆分,分别放入循环控制逻辑之中。

如上面所举的例子可以写为:
拆分成多个循环体,确实会增加循环的overhead,多出额外的循环计数操作和分支判断,但也会有多种“收益”。

比如可以隔离数据依赖并为并行操作和循环向量化做准备,减少每次循环中的数据引用以提高缓存命中;或者针对x86处理器,Loop distribution可以隔离循环体中的数据流,并为硬件预取(hardware prefetching)服务,当有多个CPU核的时候,也可以增加程序的并行性。

下面通过另外一个例子来测试说明。

Test Case
有如下循环代码:
分析可知,S1和S2之间存在loop-carried依赖,但因为与程序执行顺序相同的依赖关系,使得程序完全可以在执行层循环(j循环)S2之前执行S1中的计算。

据此可以将循环拆分成如下形式:
这里就完成了一次对原始循环的拆分。

在新的形式中,两个层循环(j循环)可以分别作向量化处理,以提升循环效率。

在此之后我们又可以注意到S1和S2之间仍在外层循环(i循环)中存在依赖。

同样的,我们也可以采用刚才的方式对循环再做一次变化:
如此处理之后,两个循环体就成为了完全独立的可以并行操作的循环。

但是否这样就一定会带来性能的提升?答案是不一定。

可以明显地看出,虽然循环体不再因为数据依赖而必须顺序执行,但也引入了大量的overhead。

收益是否一定大于损失,存在很多影响的因素。

性能优化很多时候需要掌握平衡,一种优化方式是否真正有效,首先需要抓住主要矛盾,同时也需要实际的实验来最终保证。

请读者采用不同的gcc优化参数来实际测试性能的变化。

同时也应注意到,如果在原始代码中,将S1和S2交换一下位置,我们是不能直接做这种拆分的。

虽然Loop distribution并不一定可以带来性能的提升,但这也用反例的形式说明了另外一种优化技巧——Loop fusion。

如同乘法和除法一样,Loop fusion 和Loop distribution也是一对逆运算。

Loop fusion是将拆分的循环结构合并到一起,如果将上面的例子中,loop distribution拆分出来的代码看作是原始代码,那么最初的原始代码就是Loop fusion处理过后的代码。

Loop fusion的优劣也正好和Loop distribution相反。

它减少了循环的overhead,但也可能会引入不必要的依赖。

但如果可以以提高数据本地化程度等方式提升整体性能,也不失为一种有效的选择。

对于循环的优化,还有很多种方式,例如loop unrolling,loop interchange 等等,这些技巧就留待下一期再行介绍。

作者简介:攀,云杉网络工程师,专注于x86网络软件的开发与性能优化,深度参与ONF/OPNFV/ONOS等组织及社区,曾任ONF测试工作组副主席。

相关文档
最新文档