为什么gcc总是制造jle/jg?

为什么gcc总是制造jle/jg?,gcc,optimization,assembly,Gcc,Optimization,Assembly,我做了一些汇编测试代码,只是和字符比较一下, 无论条件是否相等,gcc总是使jle/jg组合 例1 if('A'

我做了一些汇编测试代码,只是和字符比较一下, 无论条件是否相等,gcc总是使jle/jg组合


例1

if('A'

0x000000000040054d <+32>:   cmp    BYTE PTR [rbp-0x1],0x41

0x0000000000400551 <+36>:   jle    0x40056a <main+61>

0x0000000000400553 <+38>:   cmp    BYTE PTR [rbp-0x1],0x59

0x0000000000400557 <+42>:   jg     0x40056a <main+61>
0x000000000040054d <+32>:   cmp    BYTE PTR [rbp-0x1],0x40

0x0000000000400551 <+36>:   jle    0x40056a <main+61>

0x0000000000400553 <+38>:   cmp    BYTE PTR [rbp-0x1],0x5a

0x0000000000400557 <+42>:   jg     0x40056a <main+61>
0x000000000040054d:cmp字节PTR[rbp-0x1],0x41
0x0000000000400551:jle 0x40056a
0x0000000000400553:cmp字节PTR[rbp-0x1],0x59
0x0000000000400557:jg 0x40056a

例2


如果('A'可以看到第一次比较是针对[x41…x59]范围。第二次比较是针对[x40…x5a]范围。基本上,编译器将其转换为

if ( 'A'-1 < test && test < 'Z'+1 )
if('A'-1
然后生成相同的代码

更新

为了说明为什么我认为编译器更喜欢JL而不是JLE。JLE取决于正在更新的标志值(ZF=1),但JL没有。因此,JLE将引入可能会损害指令级并行性的依赖项,即使指令计时本身是相同的


因此,请明确选择-转换代码以使用更简单的指令。

通常,您不能强制编译器发出特定指令。在这种情况下,如果您去掉常量,编译器将无法调整它,则可能会成功。请注意,由于表达式的性质,编译器可能仍会反转其中一条指令测试,并因此带来一个相等值。您可以通过使用
goto
来解决这个问题。显然,这两个更改都会生成更糟糕的代码。

问题到底是什么?如果编译器提供了最快的选项,为什么您需要不同的代码?编译器基本上会将第二次比较转换为
if('A'-1<测试和测试<'Z'+1)
并生成相同的代码优化或编译器没有问题。它生成的是正确的代码。您不能指定编译器用于实现任何给定语句的指令。它可以使用目标CPU支持的任何指令,只要给出正确的结果。@cheesechoi啊,我明白您的意思。好吧,这可能是因为JLE对ZF=1进行了额外的检查,因此此指令取决于正确设置和更新的标志值。这意味着即使JLE与JL占用相同的执行时间,它也具有更多的依赖性,从而使代码执行速度可能变慢,并降低指令级并行性。对于hy GCC可能正在进行此更改,请参阅:尽管此更改对x86 CPU没有影响,但它可以是对其他CPU的优化,对x86没有影响。它还可能有助于规范化比较,以便更轻松地执行其他优化。例如,将
'A'>=c | c>='c'
转换为'c!='B'。您怎么会认为在JL不依赖于标志?就此而言,是什么让你认为JL“更简单”?@StephenCanon抱歉,有点不清楚-JLE还有一个额外的依赖性。
就此而言,是什么让你认为JL“更简单”?
好吧,JLE有两个条件需要检查,请参见表2“表面复杂度不可承受”,CMP+JL和CMP+JLE的实际性能是完全相同的,依赖项也是完全相同的。@StephenCanon作为一条指令是的,我提到过这一点。如果它们完全相同,那么您认为编译器为什么要将转换应用于条件表达式?这是因为它将间隔从[a…b]扩展到[a-1…b+1],它不是突然发生的。CMP写入所有标志。因此,JL和JLE都只依赖于CMP的结果,而不依赖于任何以前的状态。时间间隔被扩展是因为编译器规范化了比较,并且碰巧选择了特定的规范化,而不是因为这样或那样更快。