评估循环变量增量的成本很高

时间:2014-10-30 16:50:20

标签: performance loops assembly

我们在Fortran中有一个科学代码,我们正在使用VTune进行一些热点分析。它识别的最大热点是END DO。深入到汇编代码中,END DO上花费的时间的1/4在jle命令中。剩余的3/4位于循环索引变量的incq命令中。

END DO行分为addqincqcmpqjle,我希望比较运算符的排长时间最长。

但令我感到困惑的是为什么incq花了这么长时间。在这种特殊情况下,循环执行12次,仅在incq上花费120毫秒的总时间。为什么增加索引变量需要这么长时间?

这有点乱,但这里是原始循环,其中初始数字是与汇编代码对应的行号:

796            DO NS = 1, NSPECI
857               IF (Tguess(iter) < Tmid(ns)) THEN
858                  M = 1
859               ELSE
860                  M = 2
861               END IF
862
863               Cpp = thcoef(1,m,ns)
864               Hfp = thcoef(1,m,ns) * Tguess(iter)
865
866               a_nTn = thcoef(2,m,ns) * Tguess(iter)
867               Cpp = Cpp + a_nTn
868               Hfp = Hfp + R2I * a_nTn * Tguess(iter)
869
870               a_nTn = thcoef(3,m,ns) * T2
871               Cpp = Cpp + a_nTn
872               Hfp = Hfp + R3I * a_nTn * Tguess(iter)
873
874               a_nTn = thcoef(4,m,ns) * T3
875               Cpp = Cpp + a_nTn
876               Hfp = Hfp + R4I * a_nTn * Tguess(iter)
877
878               a_nTn = thcoef(5,m,ns) * T4
879               Cpp = Cpp + a_nTn
880               Hfp = Hfp + R5I * a_nTn * Tguess(iter) + thcoef(6,m,ns)
881
883               CVP = CPP - 1.0_wp
884               hf = hf + hfp * Rgk_Yk(ns)
885               Cv = Cv + Cvp * Rgk_Yk(ns)
886            END DO

和生成的程序集:

        movq      112(%rax), %rdx                               #863.22
        movq      104(%rax), %r15                               #863.22
        imulq     %r15, %rdx                                    #
        movq      88(%rax), %rsi                                #863.22
        movq      80(%rax), %rcx                                #863.22
        imulq     %rcx, %rsi                                    #
        movq      %r8, -176(%rbp)                               #
        movq      thermo_m_mp_thcoef_(%rip), %r8                #863.16
        movq      64+thermo_m_mp_tmid_(%rip), %r14              #857.20
        subq      %rdx, %r8                                     #
        shlq      $3, %r14                                      #
        subq      %rsi, %r8                                     #
        movq      %rdi, -392(%rbp)                              #
        negq      %r14                                          #
        movq      64+thermo_m_mp_thcoef_(%rip), %rdi            #863.16
        shlq      $3, %rdi                                      #
        subq      %rdi, %r8                                     #
        movq      %rcx, -248(%rbp)                              #863.22
        movq      %r8, -240(%rbp)                               #
        movsd     .L_2il0floatpacket.46(%rip), %xmm6            #868.32
        movsd     .L_2il0floatpacket.47(%rip), %xmm5            #872.32
        movsd     .L_2il0floatpacket.48(%rip), %xmm4            #876.32
        movsd     .L_2il0floatpacket.49(%rip), %xmm3            #880.32
        pushq     $1                                            #796.13
        popq      %rcx                                          #796.13
        movsd     -280(%rbp), %xmm8                             #
        movq      %r15, %rdx                                    #
        movsd     -208(%rbp), %xmm2                             #
        movsd     -264(%rbp), %xmm1                             #
        movsd     .L_2il0floatpacket.52(%rip), %xmm7            #
        movsd     .L_2il0floatpacket.46(%rip), %xmm6            #
        movsd     .L_2il0floatpacket.47(%rip), %xmm5            #
        movsd     .L_2il0floatpacket.48(%rip), %xmm4            #
        movsd     .L_2il0floatpacket.49(%rip), %xmm3            #
        testq     %r12, %r12                                    #796.13
        jle       ..B10.15      # Prob 2%                       #796.13
..B10.13:                       # Preds ..B10.25 ..B10.13
        movsd     (%r14,%rcx,8), %xmm10                         #857.35
        movaps    %xmm2, %xmm15                                 #864.16
        xorq      %rax, %rax                                    #863.16
        comisd    %xmm2, %xmm10                                 #863.16
        setbe     %al                                           #863.16
        incq      %rax                                          #863.16
        imulq     -248(%rbp), %rax                              #863.22
        addq      -240(%rbp), %rax                              #863.16
        movsd     16(%rax,%rdx), %xmm11                         #866.24
        mulsd     %xmm2, %xmm11                                 #866.16
        movsd     8(%rax,%rdx), %xmm10                          #863.16
        movsd     24(%rax,%rdx), %xmm12                         #870.24
        mulsd     %xmm10, %xmm15                                #864.16
        addsd     %xmm11, %xmm10                                #867.16
        mulsd     %xmm6, %xmm11                                 #868.32
        mulsd     -232(%rbp), %xmm12                            #870.16
        mulsd     %xmm2, %xmm11                                 #868.40
        addsd     %xmm12, %xmm10                                #871.16
        mulsd     %xmm5, %xmm12                                 #872.32
        addsd     %xmm11, %xmm15                                #868.16
        mulsd     %xmm2, %xmm12                                 #872.40
        movsd     32(%rax,%rdx), %xmm13                         #874.24
        addsd     %xmm12, %xmm15                                #872.16
        mulsd     %xmm1, %xmm13                                 #874.16
        movsd     40(%rax,%rdx), %xmm14                         #878.24
        addsd     %xmm13, %xmm10                                #875.16
        mulsd     %xmm4, %xmm13                                 #876.32
        mulsd     %xmm0, %xmm14                                 #878.16
        mulsd     %xmm2, %xmm13                                 #876.40
        addsd     %xmm14, %xmm10                                #879.16
        mulsd     %xmm3, %xmm14                                 #880.32
        addsd     %xmm13, %xmm15                                #876.16
        mulsd     %xmm2, %xmm14                                 #880.40
        subsd     %xmm7, %xmm10                                 #883.16
        mulsd     -8(%rbx,%rcx,8), %xmm10                       #885.30
        addsd     %xmm14, %xmm15                                #880.26
        addsd     48(%rax,%rdx), %xmm15                         #880.16
        addq      %r15, %rdx                                    #886.13
        addsd     %xmm10, %xmm9                                 #885.16
        mulsd     -8(%rbx,%rcx,8), %xmm15                       #884.30
        incq      %rcx                                          #886.13
        addsd     %xmm15, %xmm8                                 #884.16
        cmpq      %r12, %rcx                                    #886.13
        jle       ..B10.13      # Prob 82%                      #886.13

有问题END DO的说明是:

    addq      %r15, %rdx                                    #886.13
    incq      %rcx                                          #886.13
    cmpq      %r12, %rcx                                    #886.13
    jle       ..B10.13      # Prob 82%                      #886.13

在分析中,几乎没有时间花在addq上,75%的时间花在incq上。

2 个答案:

答案 0 :(得分:1)

可能发生的事情是周期会被错误的指令收费。分析通常有点不精确。或者周期因为OOO引擎的跟踪资源已满而停止等待发布的指令。

指令必须按顺序退出,因此一条旧指令(例如缓存未命中加载或存储)会在the out-of-order window has gone as far beyond it as possible时停止管道。

我假设在该循环的FP变量中有一个长循环传递的依赖链,因为没有存储,只有加载。并且我假设在顶部的(无分支)比较取决于prev迭代的结果。因此,每次迭代都必须等待前一次的结果执行。但是循环计数器指令不是这个依赖链的一部分,所以它们可以远远超过循环体的进程。 (如果有任何加载的地址只依赖于循环计数器,它们可能会远远超过使用它们的代码。)

所以很多时候,循环开销指令将是完成的最新指令,他们只是等待退出,直到超过100 uops的FP依赖链清除掉ROB(re)阶-缓冲液)。

我不确定这是否完全正确,但我认为它比inc - 慢速理论更好。英特尔手册误导了所有人,这太糟糕了。

inc 问题,除非您在P4上运行此问题。英特尔手册的那一部分是陈旧的。如果您无法用inc替换它,则只需要避免使用add(因为执行需要保留进位标记,例如{{1}循环)。

即使在P4上,adc也不会成为此代码的问题。一个错误的依赖关系不会使循环速度变慢,以至于OOO执行无法预见未来的迭代。另请注意,在循环的早期有一个inc,而且你没有说出任何关于热的内容。

请参阅 wiki了解优化指南,包括Agner Fog的优秀内容。如果你想知道为什么 inc %rax在P4上很慢,请阅读他的微体系结构指南。

答案 1 :(得分:0)

正如Jester所指出的那样,INC的使用是有问题的,因为它只对Flag寄存器进行了部分更新,因此它需要它的先前值。这反过来意味着它必须等待最后的标志写入指令才能执行。这通常会通过引入错误的依赖关系来减慢执行速度。

在这种特殊情况下,它会强制不必要地等待前一条指令的结果,以便获得INC未更新的进位标志(CF)的值。如果使用了ADD,则不会一直是对标志的读取依赖,因为ADD只将新值写入所有标志。

在这种情况下,先前的标志写入指令是ADDQ%R15,%RDX,否则INC将不依赖于此。很难说为什么这种依赖应该在这里产生如此巨大的影响(对于像DIV或MUL这样长时间运行的指令会更加明显;但是,像ADDSD和MULSD这样的SIMD指令不会影响标志)。

进位标志的值对于INC之后的指令是完全无关紧要的,因为CMPQ在没有读取的情况下覆盖了标志而且ADDSD并不关心,这就是为什么它被称为“假”&#39 ;依赖性。

即使是最新的Intel® 64 and IA-32 Architectures Optimization Reference Manual也说:

  

3.5.1.1使用INC和DEC说明

     

INC和DEC指令仅修改其中的一部分位   国旗登记。这会产生对所有先前写入的依赖   国旗登记。当这些说明时,这尤其成问题   是关键路径,因为它们用于更改地址   对于许多其他指令所依赖的负载。

     

汇编/编译器编码规则33。 (M impact,H generality)INC和   DEC指令应替换为ADD或SUB指令,   因为ADD和SUB会覆盖所有标志,而INC和DEC则不会,   因此在先前设置的指令上创建错误依赖   旗帜。

这正是Jester所说的,专业的编译器当然应该注意这些编码规则。