查看以下代码的汇编输出时(无优化,-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
最终更新
在回顾了一些后续评论之后,似乎是汉(根据Jonathan Leffler的帖子评论过)发现了这个问题。海湾合作委员会不进行优化不是因为它不能,而是因为我没有告诉它。似乎这一切都归结为IEEE浮点规则并且处理严格的条件GCC不能简单地在第一个UCOMISS之后跳转或者在第一个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
请注意,现在用一个COMISS直接替换两个UCOMISS指令,然后是JA(如果在上面跳转)和JB(如果在下面跳转)。如果你让它使用-ffast-math,GCC能够确定这个优化!
UCOMISS vs 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,则COMISS指令无效。 SNAN“。
再次感谢大家的有益讨论。
答案 0 :(得分:4)
这是另一个原因:
如果你仔细看看它,它就不是同一个表达式。
他们不是彼此的补充。因此,您无论如何都必须进行两次比较。 volatile
将强制重新加载值。
编辑:(见评论,我忘了你可以用旗帜做到这一点)
回答新问题:
从编译器的角度来看,将这两个ucomiss
组合起来并不是一个完全明显的优化。
为了组合它们,编译器必须:
ucomiss %xmm0, %xmm1
与ucomiss %xmm1, %xmm0
“相同”。所有这些都需要在编译器执行指令选择之后完成。大多数优化过程都是在指令选择之前完成的。
让我更担心的是,在你摆脱f1
之后,为什么f2
和volatiles
没有被保存在寄存器中。 -O3
真的是在给你这个吗?
答案 1 :(得分:3)
volatile
限定符意味着f1
和f2
的值可能会以编译器无法检测/期望的方式发生更改,因此每次使用f1
和f2
时都必须访问内存。 1}}或volatile
。生成的代码就是这样 - 所以它是正确的。
如果从任一变量或两个变量中删除f1
限定符,则与您获得的代码进行比较和对比。最终,您可能需要从某处读取f2
和ucomiss
的值,以避免编译器在编译时评估表达式。
在更新的代码中,movss
指令会得到两个不同的咒语,尽管前面的 ucomiss %xmm0, %xmm1
ucomiss %xmm1, %xmm0
指令是相同的:
ucomiss
对于反转条件,if (f1 > f2)
if (f1 < f2)
指令的操作数顺序颠倒过来:
{{1}}
我不相信优化器正在尽可能优化,但问题是变形超出了我的专业水平。