【发布时间】:2011-11-19 21:50:57
【问题描述】:
查看以下代码的汇编输出时(没有优化,-O2 和 -O3 产生非常相似的结果):
int main(int argc, char **argv)
{
volatile float f1 = 1.0f;
volatile float f2 = 2.0f;
if(f1 > f2)
{
puts("+");
}
else if(f1 < f2)
{
puts("-");
}
return 0;
}
GCC 做了一些我很难理解的事情:
.LC2:
.string "+"
.LC3:
.string "-"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $32, %rsp
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0x3f800000, %eax
movl %eax, -4(%rbp)
movl $0x40000000, %eax
movl %eax, -8(%rbp)
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jbe .L9
.L7:
movl $.LC2, %edi
call puts
jmp .L4
.L9:
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm1, %xmm0
jbe .L4
.L8:
movl $.LC3, %edi
call puts
.L4:
movl $0, %eax
leave
ret
为什么 GCC 将浮点值移动到 xmm0 和 xmm1 两次并且还运行 ucomiss 两次?
执行以下操作不是更快吗?
.LC2:
.string "+"
.LC3:
.string "-"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $32, %rsp
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0x3f800000, %eax
movl %eax, -4(%rbp)
movl $0x40000000, %eax
movl %eax, -8(%rbp)
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jb .L8 # jump if less than
je .L4 # jump if equal
.L7:
movl $.LC2, %edi
call puts
jmp .L4
.L8:
movl $.LC3, %edi
call puts
.L4:
movl $0, %eax
leave
ret
我根本不是真正的汇编程序员,但运行重复指令对我来说似乎很奇怪。我的代码版本有问题吗?
更新
如果你删除我原来的 volatile 并用 scanf() 替换它,你会得到相同的结果:
int main(int argc, char **argv)
{
float f1;
float f2;
scanf("%f", &f1);
scanf("%f", &f2);
if(f1 > f2)
{
puts("+");
}
else if(f1 < f2)
{
puts("-");
}
return 0;
}
以及对应的汇编器:
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
leaq -4(%rbp), %rsi
movl $.LC0, %edi
movl $0, %eax
call scanf
leaq -8(%rbp), %rsi
movl $.LC0, %edi
movl $0, %eax
call scanf
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jbe .L9
.L7:
movl $.LC1, %edi
call puts
jmp .L4
.L9:
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm1, %xmm0
jbe .L4
.L8:
movl $.LC2, %edi
call puts
.L4:
movl $0, %eax
leave
ret
最终更新
在查看了一些后续 cmets 后,han(在 Jonathan Leffler 的帖子下发表评论)似乎解决了这个问题。 GCC 不进行优化不是因为它不能,而是因为我没有告诉它。似乎这一切都归结为 IEEE 浮点规则和处理严格的条件,GCC 不能简单地在第一个 UCOMISS 之后执行高于或低于的跳转,因为它需要处理浮点数的所有特殊条件。当使用 han 对 -ffast-math 优化器的推荐时(没有一个 -Ox 标志启用 -ffast-math,因为它可能会破坏某些程序)GCC 完全符合我的要求:
以下程序集是使用 GCC 4.3.2 "gcc -S -O3 -ffast-math test.c" 生成的
.LC0:
.string "%f"
.LC1:
.string "+"
.LC2:
.string "-"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB25:
subq $24, %rsp
.LCFI0:
movl $.LC0, %edi
xorl %eax, %eax
leaq 20(%rsp), %rsi
call scanf
leaq 16(%rsp), %rsi
xorl %eax, %eax
movl $.LC0, %edi
call scanf
movss 20(%rsp), %xmm0
comiss 16(%rsp), %xmm0
ja .L11
jb .L12
xorl %eax, %eax
addq $24, %rsp
.p2align 4,,1
.p2align 3
ret
.p2align 4,,10
.p2align 3
.L12:
movl $.LC2, %edi
call puts
xorl %eax, %eax
addq $24, %rsp
ret
.p2align 4,,10
.p2align 3
.L11:
movl $.LC1, %edi
call puts
xorl %eax, %eax
addq $24, %rsp
ret
请注意,两个 UCOMISS 指令现在被替换为一个 COMISS,后跟一个 JA(如果在上面跳转)和 JB(如果在下面跳转)。如果您使用 -ffast-math 让 GCC 能够完成此优化!
UCOMISS 与 COMISS(http://www.softeng.rl.ac.uk/st/archive/SoftEng/SESP/html/SoftwareTools/vtune/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh /vc315.htm):“UCOMISS 指令与 COMISS 指令的不同之处在于,它仅在源操作数是 SNaN 时才发出无效的 SIMD 浮点异常信号。如果源操作数是 QNaN 或SNaN。”
再次感谢大家的有益讨论。
【问题讨论】:
-
有效吗?不,提出一个错误。是的,接受 GCC 开发人员在优化方面比绝大多数人更聪明的事实。 :-)
-
我认为这就是
volatile限定符的全部意义所在。删除它并进行比较。 -
我在没有 volatile 的情况下更新了我的结果。我最初放入 volatile 是为了从 scanf() 调用中清理代码。
-
“用scanf替换它”是什么意思?向我们展示 C 代码...
-
@paxdiablo,你看到了什么错误。我已经运行了组装代码,很好地测试了 xmm0 和 xmm1 的多个值,它按我的预期工作。