想象一下像这样的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
可以安全地移出循环吗?
答案 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
因此没有执行循环非切换;还有其他非常基本的优化,比如使用寄存器来保存函数参数。如果你问我,我觉得这很令人失望。