Optimization 是否可以通知编译器它';如果在环路外移动,安全吗?
想象一下这样的Fortran代码Optimization 是否可以通知编译器它';如果在环路外移动,安全吗?,optimization,compiler-construction,preprocessor,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只计算一次,但它会导致代码重复
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只计算一次,但它会导致代码重复,并且更难读取和维护
所以,我的问题是,编译器是否足够聪明,能够自行进行更改?如果
可以安全地移出循环,我能做些什么(例如使用预处理器指令)来通知编译器这个。以下是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];
}
}
带乘法的矢量化循环:
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
vs
最后一点意见:如果您想了解编译器在尝试改进循环时所做的工作,您应该了解一下这种优化称为循环取消切换。它将示例的代码转换为
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
在支持此优化的编译器中,它可以通过编译器选项启用,例如In(-loop unswitch
)和(查找-funswitch loops
)。由于您尚未指定语言,我将讨论C。说到优化,我发现C#编译器和抖动实际上都是毫无希望的!这是我测试的功能:
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" );
}
因此,没有执行回路取消切换;还有其他一些非常基本的优化,比如使用寄存器保存函数参数。如果你问我,我觉得这很令人失望。谢谢你的测试和评论。这看起来很有希望,但可能代码太简单,无法成为一个现实的测试:)@Jellby那么你应该想出一个现实的测试,因为这个答案非常好。顺便说一句,如果将某些内容移出循环对于提高性能非常重要,而您不信任自己的编译器,那么您应该将if语句移出循环。@Ali是的,当然,我的评论就是这个意思:我的示例可以改进。我将自己做一些进一步的测试,看看这个答案(正如你所说的,非常好)是否足够,或者我能想出更好的答案。顺便说一下,这两个循环都不是矢量化的:它们都是单浮点指令。是的,你是对的。谢谢我没有注意,因为使用了xmm
寄存器。我没有复制代码的正确部分。我正在修理我的岗位。
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
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" );
}
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