是否有可能通知编译器在循环外移动IF是安全的?

时间:2014-02-22 08:02:06

标签: optimization compiler-construction preprocessor

想象一下像这样的Fortran代码

DO i=1,n
  IF (alpha == 0.0) THEN
    x(i) = y(i)
  ELSE
    x(i) = alpha*x(i)+y(i)
  END IF
END DO

(这当然只是一个愚蠢的例子,在实际情况下,循环将更复杂,并且不能像数组结构那样容易重写)

由于alpha是一个常量,IF语句可以移到外面并有两个不同的循环,一个用于alpha==0,另一个用于其他情况。这应该更有效率,因为IF仅被评估一次,但它会导致代码重复并使其更难以阅读和维护。

所以,我的问题是,编译器通常是否足够聪明,可以自行进行更改?我可以做任何事情(例如预处理器指令)通知编译器这个IF可以安全地移出循环吗?

3 个答案:

答案 0 :(得分:4)

我不知道Fortran但是,当被告知优化时,C编译器通常足够聪明。以下是GCC对以下等效C代码的处理:

void foo(int n, float x[], float y[], float alpha) 
{
  int i;
  for (i=0; i<n; i++) 
    {
      if (alpha == 0.0) x[i] = y[i];
      else x[i] = alpha*x[i]+y[i];
    }
}

以下是编译代码的两个摘录,优化级别2(-O3)。评论是我的。

没有乘法的矢量化循环:

    movl    %edi, %r8d              ; %r8d = n
    xorps   %xmm1, %xmm1            ; clear xmm1
    shrl    $2, %r8d                ; %r8d = n >> 2
    xorl    %eax, %eax              ; clear eax = pointeur increment 
    xorl    %ecx, %ecx              ; clear ecx = (i>>2)
    leal    0(,%r8,4), %r9d         ; not relevant here
L19:
    movaps  %xmm1, %xmm0            ; xmm0 = 0
    addl    $1, %ecx                ; (i>>2) ++
    movlps  (%rdx,%rax), %xmm0      ; Load floats into xmm0 (vector registers)
    movhps  8(%rdx,%rax), %xmm0     ; Load floats into xmm0 (vector registers)
    movlps  %xmm0, (%rsi,%rax)      ; store floats in xmm0 into memory
    movhps  %xmm0, 8(%rsi,%rax)     ; store floats in xmm0 into memory
    addq    $16, %rax               ; increment pointer by 16
    cmpl    %r8d, %ecx              ; if (i>>2) < (n>>2)
    jb      .L19                    ; go back to .L19
                                    ; else finish the non vectorized part of the loop

带乘法的矢量化循环:

    movaps  %xmm0, %xmm4            ; alpha -> xmm4
    movl    %edi, %r8d              ; %r8d = n
    shrl    $2, %r8d                ; %r8d = n >> 2
    xorps   %xmm3, %xmm3            ; clear xmm3
    shufps  $0, %xmm4, %xmm4        ; distribute xmm4 to all vector elements
    leal    0(,%r8,4), %r9d         ; not relevant here
    xorl    %eax, %eax              ; clear eax = pointeur increment 
    xorl    %ecx, %ecx              ; clear ecx = (i>>2)
.L11:
    movaps  %xmm3, %xmm1            ; xmm1 = 0
    addl    $1, %ecx                ; (i>>2) ++
    movaps  %xmm3, %xmm2            ; xmm2 = 0
    movlps  (%rsi,%rax), %xmm1      ; Load floats X into xmm1 (vector registers)
    movlps  (%rdx,%rax), %xmm2      ; Load floats Y into xmm2 (vector registers)
    movhps  8(%rsi,%rax), %xmm1     ; Load floats X into xmm1 (vector registers)
    movhps  8(%rdx,%rax), %xmm2     ; Load floats Y into xmm2 (vector registers)
    mulps   %xmm4, %xmm1            ; multiply xmm1 by xmm4
    addps   %xmm2, %xmm1            ; add xmm2 to xmm1
    movlps  %xmm1, (%rsi,%rax)      ; store floats in xmm1 into memory
    movhps  %xmm1, 8(%rsi,%rax)     ; store floats in xmm1 into memory
    addq    $16, %rax               ; increment pointer by 16
    cmpl    %r8d, %ecx              ; if i>>2 < n>>2 then
    jb      .L11                    ; go back to .L19
                                    ; else finish the non vectorized part of the loop

由于向量化,代码会有更多的代码,如果n不是4的倍数,则强制执行某些操作。无论如何,显然有两个独立的循环,其中一个没有乘法。我没有看到任何理由为什么Fortran编译器不能这样做。

所以答案是我不知道如何告诉编译器它可以做到这一点,除非我自己进行转换,但是,编译器足够聪明,可以做到。他们甚至有时交换

中的独立循环
for i in 1..n for j in 1..n

VS

for j in 1..n for i in 1..n

最后一条评论:如果你想了解编译器知道如何改进循环,你应该看一下http://en.wikipedia.org/wiki/Polytope_model

答案 1 :(得分:2)

此优化称为循环非开关。它将示例的代码转换为

IF (alpha == 0.0) THEN
    DO i=1,n
        x(i) = y(i)
    END DO
ELSE
    DO i=1,n
       x(i) = alpha*x(i)+y(i)
    END DO
END IF

在支持此优化的编译器中,可以通过编译器选项启用它,例如,在LLVM-loop-unswitch)和gcc中(查找-funswitch-loops)。

答案 2 :(得分:0)

由于您还没有指定语言,我将谈谈C#。在优化方面,我发现C#编译器和JITter实际上都是绝望的!这是我测试的功能:

static void foo( int n, float[ ] x, float[ ] y, float alpha ) {
    System.Diagnostics.Debugger.Break( );

    for ( int i = 0; i < n; i++ ) {
        if ( alpha == 0.0 ) x[ i ] = y[ i ];
        else x[ i ] = alpha * x[ i ] + y[ i ];
    }

    Console.WriteLine( "END" );
}

这就是它的拆卸。它是在32位模式下编译的,用于带有VS2010的.NET 4.0,并启用了优化:

001F00C3    57              PUSH EDI
001F00C4    56              PUSH ESI
001F00C5    53              PUSH EBX
001F00C6    83EC 0C         SUB ESP,0C
001F00C9    8BF9            MOV EDI,ECX
001F00CB    8BF2            MOV ESI,EDX
001F00CD    8B5D 0C         MOV EBX,DWORD PTR SS:[EBP+0C]
001F00D0    D945 08         FLD DWORD PTR SS:[EBP+8]
001F00D3    D95D F0         FSTP DWORD PTR SS:[EBP-10]

                 System.Diagnostics.Debugger.Break( );
001F00D6    E8 9D7C915C     CALL 5CB07D78
001F00DB    D945 F0         FLD DWORD PTR SS:[EBP-10]
001F00DE    33D2            XOR EDX,EDX
001F00E0    85FF            TEST EDI,EDI
001F00E2    7F 04           JG SHORT 001F00E8
001F00E4    DDD8            FSTP ST
001F00E6    EB 45           JMP SHORT 001F012D

                 if ( alpha == 0.0 )
001F00E8    D9C0            FLD ST
001F00EA    DD5D E8         FSTP QWORD PTR SS:[EBP-18]
001F00ED    DD45 E8         FLD QWORD PTR SS:[EBP-18]
001F00F0    D9EE            FLDZ
001F00F2    DFF1            FCOMIP ST,ST(1)
001F00F4    DDD8            FSTP ST
001F00F6    7A 16           JPE SHORT 001F010E
001F00F8    75 14           JNE SHORT 001F010E

                 x[ i ] = y[ i ]
001F00FA    3B53 04         CMP EDX,DWORD PTR DS:[EBX+4]
001F00FD    73 4D           JNB SHORT 001F014C
001F00FF    D94493 08       FLD DWORD PTR DS:[EDX*4+EBX+8]
001F0103    3B56 04         CMP EDX,DWORD PTR DS:[ESI+4]
001F0106    73 44           JNB SHORT 001F014C
001F0108    D95C96 08       FSTP DWORD PTR DS:[EDX*4+ESI+8]
001F010C    EB 18           JMP SHORT 001F0126

                 else x[ i ] = alpha * x[ i ] + y[ i ];
001F010E    D9C0            FLD ST
001F0110    3B56 04         CMP EDX,DWORD PTR DS:[ESI+4]
001F0113    73 37           JNB SHORT 001F014C
001F0115    D84C96 08       FMUL DWORD PTR DS:[EDX*4+ESI+8]
001F0119    3B53 04         CMP EDX,DWORD PTR DS:[EBX+4]
001F011C    73 2E           JNB SHORT 001F014C
001F011E    D84493 08       FADD DWORD PTR DS:[EDX*4+EBX+8]
001F0122    D95C96 08       FSTP DWORD PTR DS:[EDX*4+ESI+8]

                 End of loop body
001F0126    42              INC EDX
001F0127    3BD7            CMP EDX,EDI
001F0129    7C BD           JL SHORT 001F00E8

                 Console.WriteLine( "END" );
001F012B    DDD8            FSTP ST
001F012D    E8 12F9305C     CALL 5C4FFA44
001F0132    8BC8            MOV ECX,EAX
001F0134    8B15 30201203   MOV EDX,DWORD PTR DS:[3122030]
001F013A    8B01            MOV EAX,DWORD PTR DS:[ECX]
001F013C    8B40 3C         MOV EAX,DWORD PTR DS:[EAX+3C]
001F013F    FF50 10         CALL DWORD PTR DS:[EAX+10]

                 return;
001F0142    8D65 F4         LEA ESP,[EBP-0C]
001F0145    5B              POP EBX
001F0146    5E              POP ESI
001F0147    5F              POP EDI
001F0148    5D              POP EBP
001F0149    C2 0800         RETN 8

因此没有执行循环非切换;还有其他非常基本的优化,比如使用寄存器来保存函数参数。如果你问我,我觉得这很令人失望。