汇编代码优化入门&&数据流 CSapp第五章

 

汇编代码优化的局限性

  对于:

  这样的一段代码,我们很容易想到优化成:

  这样,然而这样优化是错误的,当且仅当xp和yp指向同一块内存单元。

消除不必要的内存引用

  如下图,有这样一段代码:

  我对于SSE指令集不太熟悉。这和普通的asm汇编还是有很大区别的。SSE指令集的全称是Streaming SIMD Extensions,课件这个指令集和SIMD有很大的关系。它是一种在MMX基础上发展出来的SIMD指令集,其不再占用浮点寄存器,而是使用单独的128位XMM寄存器。在此基础上又发展除了SSE2/SSE3/SSE4指令集。SSE2则进一步支持双精度浮点数,由于寄存器长度没有变长,所以只能支持2个双精度浮点计算或是4个单精度浮点计算,另外,它在这组寄存器上实现了整型计算,从而代替了MMX。SSE3支持一些更加复杂的算术计算。SSE4增加了更多指令,并且在数据搬移上下了一番工夫,支持不对齐的数据搬移,增加了super shuffle引擎等。

  xmm0是SSE当中的128位寄存器。

  可以看到,源数据的地址指针保存在%rbx寄存器当中,每次读取到%xmm0寄存器当中。

  rdx是始终指向data[i]的,可以看到rdx在循环当中每次都会加8,也就是说数据大小位8字节,64位。

  vmulsd   (%rdx)  ,%xmm0,%xmm0

  这句代码是将data[i]乘以源数据的值,然后将中间结果保存到%xmm0当中。

  下一句vmovsd %xmm0 ,(%rbx)将数据写回dst当中去。

 

  可以看出这样的代码是十分冗余的,只需要这样:

  就能够完成同样的功能。

重中之重——利用处理器微体系结构优化

  经典的使代码适应机器。

  数据流(data-flow)概念的引入:

  然后我们就来学习一下如何从机器级代码得到数据流图

  如下图代码:

 

   数据流图为:

 

  最上面是寄存器开始的值,最下面是寄存器结束之后的值。(%rdx)涉及到针对内存的读取,对应的是右边的load操作。

  我们需要从这张图上发掘数据依赖关系

  

  cmp和jne属于分支预测环节,假如采用分支预测技术,那么就可以假定这两个环节不影响。另外rax的值一直没有改变。

  所以程序执行流程最终可以简化为:

  

 

  为什么乘法操作序列会成为关键路径呢?

循环展开

  经典的优化方法。

  利用数据流图我们可以更好地认识到循环展开是怎么优化的。

  对于代码:

 

  可以得到数据流图:

  

  重新排列:

 

 

   最终获得:

 

  可以看出来,循环展开并没有优化乘法运算的性能。关键路径上任然是mul-load操作的密集排列。

  书上的

 

  这张图也说明了这一点。

 

提高并行----使用多个累积变量

  观察前面的关键路径上的mul-load序列,我们尝试想一下:能不能在mul的时候load?

  可以!再提供一个累积变量xmm1即可!

  代码如下:

 

 

 

 

  重新排列:

  最终:

  显然代码得到了优化。

重新结合变换

  前面的运算其实就是累乘运算。

  原本的结合顺序是:

  现在改变为:

  这就叫做重新结合变换

  

  就是1*2*3*4*5*6*7*8=(1*2)*(3*4)*(5*6)*(7*8)  (遗憾的是不满足结合律的运算无法这样使用

  

 课后作业5.13

  

 

 

 

A:数据流图如下图所示:

      

       形成关键路径:

 

 

 

B:

对于浮点数加法运算,CPE的下界为3

C:

对于整数加法运算,CPE运算的下界为1

D:

  浮点数乘法在该数据流图当中,不存在数据依赖。因为在上图的这个算子当中,各个算子之间只有加法存在数据依赖,也就是下一个算子执行add运算之前,必须先得到前一个算子的add执行结果。

    但是对于浮点数乘法运算来说,各个算子当中的乘法运算都是不相关的,可以各自独立进行。故而运算CPE下界由3.0的浮点数加法运算决定。

   

  我觉得一个关键路径就是一个算子。算子之间才考虑运算的数据依赖关系。上题的乘法运算可以通过SIMD等方法来解决,但是add的关键路径操作是无论如何都无法解决的。

posted @ 2021-12-06 14:17  TheDa  阅读(774)  评论(0编辑  收藏  举报