在Java中,可以比&&?更快。

在此代码中:

if (value >= x && value <= y) {

当 和 在没有特定模式的情况下与 false 一样可能为真时,使用 & 运算符会比使用 &&?value >= xvalue <= y

具体来说,我正在考虑如何懒惰地评估右侧表达式(即仅当LHS为真时),这意味着条件表达式,而在此上下文中的Java中保证对两个(布尔)子表达式的严格评估。无论采用哪种方式,值结果都是相同的。&&&

但是,虽然 or 运算符将使用简单的比较指令,但必须涉及一个分支,并且该分支容易受到分支预测失败的影响 - 根据这个非常著名的问题:为什么处理排序数组比处理未排序数组更快?>=<=&&

因此,强制表达式没有惰性组件肯定会更具确定性,并且不容易受到预测失败的影响。右?

笔记:

  • 显然,如果代码看起来像这样,我问题的答案将是“否”:。我专注于“足够简单”的 RHS 表达式。if(value >= x && verySlowFunction())
  • 无论如何,那里有一个条件分支(语句)。我不能完全向自己证明这是无关紧要的,替代公式可能是更好的例子,比如ifboolean b = value >= x && value <= y;
  • 这一切都落入了可怕的微优化世界。是的,我知道:-)...有趣吗?

更新只是为了解释为什么我感兴趣:我一直在盯着马丁·汤普森(Martin Thompson)在他的机械同情博客上写的系统,在他来做一个关于Aeron的演讲之后。其中一个关键信息是,我们的硬件中包含所有这些神奇的东西,而我们软件开发人员却悲惨地未能利用它。别担心,我不会在我所有的代码上去s/&/\&/:-)...但是这个站点上有很多关于通过删除分支来改善分支预测的问题,并且我突然想到条件布尔运算符是测试条件的核心

当然,@StephenC提出了一个奇妙的观点,即将代码转换为奇怪的形状会使JIT不容易发现常见的优化 - 如果不是现在,那么将来。上面提到的“非常有名的问题”是特别的,因为它将预测的复杂性远远超出了实际优化。

我非常清楚,在大多数(或几乎所有)情况下,这是最清晰,最简单,最快速,最好的事情 - 尽管我非常感谢那些发布答案证明这一点的人!我真的很想看看在任何人的经验中,是否真的有任何情况,“可以更快?”的答案可能是肯定的......&&&

更新2(解决有关问题过于宽泛的建议。我不想对这个问题进行重大更改,因为它可能会损害下面的一些答案,这些答案质量非常出色!也许需要在野外树立一个榜样;这是来自番石榴龙马级(非常感谢@maaartinus找到这个):

public static boolean isPowerOfTwo(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}

看到第一个?如果您检查链接,则称为 ,这暗示我们处于分支回避领域 - 番石榴确实被广泛使用:保存的每个周期都会导致海平面明显下降。因此,让我们这样提出问题:这种使用&(哪里&&会更正常)是真正的优化吗?&lessThanBranchFree(...)


答案 1

好吧,所以你想知道它在较低级别的行为...那么让我们来看看字节码吧!

编辑:在最后添加了为AMD64生成的汇编代码。看看一些有趣的笔记。
编辑2(re:OP的“更新2”):还为番石榴的isPowerOfTwo方法添加了asm代码。

Java 源代码

我写了这两个快速方法:

public boolean AndSC(int x, int value, int y) {
    return value >= x && value <= y;
}

public boolean AndNonSC(int x, int value, int y) {
    return value >= x & value <= y;
}

如您所见,除了 AND 运算符的类型外,它们完全相同。

Java 字节码

这是生成的字节码:

  public AndSC(III)Z
   L0
    LINENUMBER 8 L0
    ILOAD 2
    ILOAD 1
    IF_ICMPLT L1
    ILOAD 2
    ILOAD 3
    IF_ICMPGT L1
   L2
    LINENUMBER 9 L2
    ICONST_1
    IRETURN
   L1
    LINENUMBER 11 L1
   FRAME SAME
    ICONST_0
    IRETURN
   L3
    LOCALVARIABLE this Ltest/lsoto/AndTest; L0 L3 0
    LOCALVARIABLE x I L0 L3 1
    LOCALVARIABLE value I L0 L3 2
    LOCALVARIABLE y I L0 L3 3
    MAXSTACK = 2
    MAXLOCALS = 4

  // access flags 0x1
  public AndNonSC(III)Z
   L0
    LINENUMBER 15 L0
    ILOAD 2
    ILOAD 1
    IF_ICMPLT L1
    ICONST_1
    GOTO L2
   L1
   FRAME SAME
    ICONST_0
   L2
   FRAME SAME1 I
    ILOAD 2
    ILOAD 3
    IF_ICMPGT L3
    ICONST_1
    GOTO L4
   L3
   FRAME SAME1 I
    ICONST_0
   L4
   FRAME FULL [test/lsoto/AndTest I I I] [I I]
    IAND
    IFEQ L5
   L6
    LINENUMBER 16 L6
    ICONST_1
    IRETURN
   L5
    LINENUMBER 18 L5
   FRAME SAME
    ICONST_0
    IRETURN
   L7
    LOCALVARIABLE this Ltest/lsoto/AndTest; L0 L7 0
    LOCALVARIABLE x I L0 L7 1
    LOCALVARIABLE value I L0 L7 2
    LOCALVARIABLE y I L0 L7 3
    MAXSTACK = 3
    MAXLOCALS = 4

正如预期的那样,() 方法生成个条件跳转:AndSC&&

  1. 它加载并加载到堆栈上,如果较低,则跳到L1。否则,它将继续运行下一行。valuexvalue
  2. 它加载并加载到堆栈上,如果更大,也会跳到L1。否则,它将继续运行下一行。valueyvalue
  3. 这恰好是一个以防万一的两个跳跃都没有完成。return true
  4. 然后我们有标记为L1的行,这是一个.return false

但是,() 方法生成个条件跳转!AndNonSC&

  1. 它加载并加载到堆栈上,如果较低,则跳到L1。因为现在它需要保存结果以将其与AND的另一部分进行比较,因此它必须执行“save”或“save”,它不能用相同的指令同时执行这两件事。valuexvaluetruefalse
  2. 它加载并加载到堆栈上,如果更大,则跳到L1。再一次,它需要保存或,这是两条不同的线,具体取决于比较结果。valueyvaluetruefalse
  3. 现在两个比较都完成了,代码实际上执行 AND 操作 - 如果两者都为真,则跳转(第三次)返回 true;否则,它继续执行到下一行以返回 false。

(初步)结论

虽然我对Java字节码不是很有经验,而且我可能忽略了一些东西,但在我看来,它实际上会比每种情况下都表现得更差:它生成更多的指令来执行,包括更多的条件跳转来预测,并且可能会失败。&&&

正如其他人所建议的那样,重写代码以用算术运算替换比较,可能是做出更好选择的一种方式,但代价是使代码不那么清晰。
恕我直言,对于99%的场景来说,这不值得麻烦(但是,对于需要进行极度优化的1%循环来说,这可能是非常值得的)。&

编辑:AMD64组装

如注释中所述,相同的Java字节码可能导致不同系统中的不同机器代码,因此,虽然Java字节码可能会给我们一个关于哪个AND版本性能更好的提示,但获取编译器生成的实际ASM是真正找出答案的唯一方法。
我打印了这两种方法的AMD64 ASM说明;以下是相关行(剥离的入口点等)。

注意:除非另有说明,否则所有使用 java 1.8.0_91 编译的方法。

具有默认选项的方法和SC

  # {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002923e3e: cmp    %r8d,%r9d
  0x0000000002923e41: movabs $0x16da0a08,%rax   ;   {metadata(method data for {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest')}
  0x0000000002923e4b: movabs $0x108,%rsi
  0x0000000002923e55: jl     0x0000000002923e65
  0x0000000002923e5b: movabs $0x118,%rsi
  0x0000000002923e65: mov    (%rax,%rsi,1),%rbx
  0x0000000002923e69: lea    0x1(%rbx),%rbx
  0x0000000002923e6d: mov    %rbx,(%rax,%rsi,1)
  0x0000000002923e71: jl     0x0000000002923eb0  ;*if_icmplt
                                                ; - AndTest::AndSC@2 (line 22)

  0x0000000002923e77: cmp    %edi,%r9d
  0x0000000002923e7a: movabs $0x16da0a08,%rax   ;   {metadata(method data for {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest')}
  0x0000000002923e84: movabs $0x128,%rsi
  0x0000000002923e8e: jg     0x0000000002923e9e
  0x0000000002923e94: movabs $0x138,%rsi
  0x0000000002923e9e: mov    (%rax,%rsi,1),%rdi
  0x0000000002923ea2: lea    0x1(%rdi),%rdi
  0x0000000002923ea6: mov    %rdi,(%rax,%rsi,1)
  0x0000000002923eaa: jle    0x0000000002923ec1  ;*if_icmpgt
                                                ; - AndTest::AndSC@7 (line 22)

  0x0000000002923eb0: mov    $0x0,%eax
  0x0000000002923eb5: add    $0x30,%rsp
  0x0000000002923eb9: pop    %rbp
  0x0000000002923eba: test   %eax,-0x1c73dc0(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923ec0: retq                      ;*ireturn
                                                ; - AndTest::AndSC@13 (line 25)

  0x0000000002923ec1: mov    $0x1,%eax
  0x0000000002923ec6: add    $0x30,%rsp
  0x0000000002923eca: pop    %rbp
  0x0000000002923ecb: test   %eax,-0x1c73dd1(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923ed1: retq   

方法和SC-XX:打印Assembly选项=英特尔选项

  # {method} {0x00000000170a0810} 'AndSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002c26e2c: cmp    r9d,r8d
  0x0000000002c26e2f: jl     0x0000000002c26e36  ;*if_icmplt
  0x0000000002c26e31: cmp    r9d,edi
  0x0000000002c26e34: jle    0x0000000002c26e44  ;*iconst_0
  0x0000000002c26e36: xor    eax,eax            ;*synchronization entry
  0x0000000002c26e38: add    rsp,0x10
  0x0000000002c26e3c: pop    rbp
  0x0000000002c26e3d: test   DWORD PTR [rip+0xffffffffffce91bd],eax        # 0x0000000002910000
  0x0000000002c26e43: ret    
  0x0000000002c26e44: mov    eax,0x1
  0x0000000002c26e49: jmp    0x0000000002c26e38

具有默认选项的方法和非SC

  # {method} {0x0000000016da0908} 'AndNonSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002923a78: cmp    %r8d,%r9d
  0x0000000002923a7b: mov    $0x0,%eax
  0x0000000002923a80: jl     0x0000000002923a8b
  0x0000000002923a86: mov    $0x1,%eax
  0x0000000002923a8b: cmp    %edi,%r9d
  0x0000000002923a8e: mov    $0x0,%esi
  0x0000000002923a93: jg     0x0000000002923a9e
  0x0000000002923a99: mov    $0x1,%esi
  0x0000000002923a9e: and    %rsi,%rax
  0x0000000002923aa1: cmp    $0x0,%eax
  0x0000000002923aa4: je     0x0000000002923abb  ;*ifeq
                                                ; - AndTest::AndNonSC@21 (line 29)

  0x0000000002923aaa: mov    $0x1,%eax
  0x0000000002923aaf: add    $0x30,%rsp
  0x0000000002923ab3: pop    %rbp
  0x0000000002923ab4: test   %eax,-0x1c739ba(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923aba: retq                      ;*ireturn
                                                ; - AndTest::AndNonSC@25 (line 30)

  0x0000000002923abb: mov    $0x0,%eax
  0x0000000002923ac0: add    $0x30,%rsp
  0x0000000002923ac4: pop    %rbp
  0x0000000002923ac5: test   %eax,-0x1c739cb(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923acb: retq   

方法和带有 -XX:打印AssemblyOptions=intel 选项

  # {method} {0x00000000170a0908} 'AndNonSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002c270b5: cmp    r9d,r8d
  0x0000000002c270b8: jl     0x0000000002c270df  ;*if_icmplt
  0x0000000002c270ba: mov    r8d,0x1            ;*iload_2
  0x0000000002c270c0: cmp    r9d,edi
  0x0000000002c270c3: cmovg  r11d,r10d
  0x0000000002c270c7: and    r8d,r11d
  0x0000000002c270ca: test   r8d,r8d
  0x0000000002c270cd: setne  al
  0x0000000002c270d0: movzx  eax,al
  0x0000000002c270d3: add    rsp,0x10
  0x0000000002c270d7: pop    rbp
  0x0000000002c270d8: test   DWORD PTR [rip+0xffffffffffce8f22],eax        # 0x0000000002910000
  0x0000000002c270de: ret    
  0x0000000002c270df: xor    r8d,r8d
  0x0000000002c270e2: jmp    0x0000000002c270c0
  • 首先,生成的ASM代码根据我们选择默认的AT&T语法还是Intel语法而有所不同。
  • 使用 AT&T 语法:
    • 对于该方法,ASM 代码实际上更长,每个字节码都转换为两个程序集跳转指令,总共有 4 个条件跳转。AndSCIF_ICMP*
    • 同时,对于该方法,编译器生成更直接的代码,其中每个字节码仅转换为一个程序集跳转指令,保持3个条件跳转的原始计数。AndNonSCIF_ICMP*
  • 使用英特尔语法:
    • 的 ASM 代码更短,只有 2 个条件跳转(不计算末尾的非条件跳转)。实际上,根据结果,它只是两个CMP,两个JL / E和一个XOR / MOV。AndSCjmp
    • 的 ASM 代码现在比一个长!但是,它只有1个条件跳转(用于第一个比较),使用寄存器直接将第一个结果与第二个结果进行比较,而无需更多的跳转。AndNonSCAndSC

ASM代码分析后的结论

  • 在AMD64机器语言级别,操作员似乎生成条件跳转较少的ASM代码,这可能更适合高预测失败率(例如随机s)。&value
  • 另一方面,运算符似乎使用较少的指令生成ASM代码(无论如何带有选项),这可能更适合具有预测友好输入的真正长循环,其中每次比较的CPU周期数较少,从长远来看可以有所作为。&&-XX:PrintAssemblyOptions=intel

正如我在一些评论中所说,这在系统之间会有很大差异,所以如果我们谈论分支预测优化,唯一真正的答案是:这取决于你的JVM实现,你的编译器,你的CPU和你的输入数据


附录:番石榴的方法isPowerOfTwo

在这里,Guava的开发人员提出了一种巧妙的方法来计算给定数字是否是2的幂:

public static boolean isPowerOfTwo(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}

引用OP:

这种使用 &(哪里 && 会更正常)是真正的优化吗?

为了确定是否是,我在我的测试类中添加了两个类似的方法:

public boolean isPowerOfTwoAND(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}

public boolean isPowerOfTwoANDAND(long x) {
    return x > 0 && (x & (x - 1)) == 0;
}

英特尔用于番石榴版本的 ASM 代码

  # {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest'
  # this:     rdx:rdx   = 'AndTest'
  # parm0:    r8:r8     = long
  ...
  0x0000000003103bbe: movabs rax,0x0
  0x0000000003103bc8: cmp    rax,r8
  0x0000000003103bcb: movabs rax,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103bd5: movabs rsi,0x108
  0x0000000003103bdf: jge    0x0000000003103bef
  0x0000000003103be5: movabs rsi,0x118
  0x0000000003103bef: mov    rdi,QWORD PTR [rax+rsi*1]
  0x0000000003103bf3: lea    rdi,[rdi+0x1]
  0x0000000003103bf7: mov    QWORD PTR [rax+rsi*1],rdi
  0x0000000003103bfb: jge    0x0000000003103c1b  ;*lcmp
  0x0000000003103c01: movabs rax,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103c0b: inc    DWORD PTR [rax+0x128]
  0x0000000003103c11: mov    eax,0x1
  0x0000000003103c16: jmp    0x0000000003103c20  ;*goto
  0x0000000003103c1b: mov    eax,0x0            ;*lload_1
  0x0000000003103c20: mov    rsi,r8
  0x0000000003103c23: movabs r10,0x1
  0x0000000003103c2d: sub    rsi,r10
  0x0000000003103c30: and    rsi,r8
  0x0000000003103c33: movabs rdi,0x0
  0x0000000003103c3d: cmp    rsi,rdi
  0x0000000003103c40: movabs rsi,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103c4a: movabs rdi,0x140
  0x0000000003103c54: jne    0x0000000003103c64
  0x0000000003103c5a: movabs rdi,0x150
  0x0000000003103c64: mov    rbx,QWORD PTR [rsi+rdi*1]
  0x0000000003103c68: lea    rbx,[rbx+0x1]
  0x0000000003103c6c: mov    QWORD PTR [rsi+rdi*1],rbx
  0x0000000003103c70: jne    0x0000000003103c90  ;*lcmp
  0x0000000003103c76: movabs rsi,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103c80: inc    DWORD PTR [rsi+0x160]
  0x0000000003103c86: mov    esi,0x1
  0x0000000003103c8b: jmp    0x0000000003103c95  ;*goto
  0x0000000003103c90: mov    esi,0x0            ;*iand
  0x0000000003103c95: and    rsi,rax
  0x0000000003103c98: and    esi,0x1
  0x0000000003103c9b: mov    rax,rsi
  0x0000000003103c9e: add    rsp,0x50
  0x0000000003103ca2: pop    rbp
  0x0000000003103ca3: test   DWORD PTR [rip+0xfffffffffe44c457],eax        # 0x0000000001550100
  0x0000000003103ca9: ret    

英特尔的 asm 代码 && version

  # {method} {0x0000000017580bd0} 'isPowerOfTwoANDAND' '(J)Z' in 'AndTest'
  # this:     rdx:rdx   = 'AndTest'
  # parm0:    r8:r8     = long
  ...
  0x0000000003103438: movabs rax,0x0
  0x0000000003103442: cmp    rax,r8
  0x0000000003103445: jge    0x0000000003103471  ;*lcmp
  0x000000000310344b: mov    rax,r8
  0x000000000310344e: movabs r10,0x1
  0x0000000003103458: sub    rax,r10
  0x000000000310345b: and    rax,r8
  0x000000000310345e: movabs rsi,0x0
  0x0000000003103468: cmp    rax,rsi
  0x000000000310346b: je     0x000000000310347b  ;*lcmp
  0x0000000003103471: mov    eax,0x0
  0x0000000003103476: jmp    0x0000000003103480  ;*ireturn
  0x000000000310347b: mov    eax,0x1            ;*goto
  0x0000000003103480: and    eax,0x1
  0x0000000003103483: add    rsp,0x40
  0x0000000003103487: pop    rbp
  0x0000000003103488: test   DWORD PTR [rip+0xfffffffffe44cc72],eax        # 0x0000000001550100
  0x000000000310348e: ret    

在这个具体的例子中,JIT编译器为该版本生成的汇编代码远远少于Guava的版本(而且,在昨天的结果之后,我真的对此感到惊讶)。
与Guava相比,该版本翻译为JIT编译的字节码减少了25%,汇编指令减少了50%,只有两个条件跳转(该版本有四个)。&&&&&&

因此,一切都表明番石榴的方法比更“自然”的版本效率低。&&&

...还是?

如前所述,我正在使用Java 8运行上述示例:

C:\....>java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)

但是,如果我切换到 Java 7 呢

C:\....>c:\jdk1.7.0_79\bin\java -version
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)
C:\....>c:\jdk1.7.0_79\bin\java -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*AndTest.isPowerOfTwoAND -XX:PrintAssemblyOptions=intel AndTestMain
  .....
  0x0000000002512bac: xor    r10d,r10d
  0x0000000002512baf: mov    r11d,0x1
  0x0000000002512bb5: test   r8,r8
  0x0000000002512bb8: jle    0x0000000002512bde  ;*ifle
  0x0000000002512bba: mov    eax,0x1            ;*lload_1
  0x0000000002512bbf: mov    r9,r8
  0x0000000002512bc2: dec    r9
  0x0000000002512bc5: and    r9,r8
  0x0000000002512bc8: test   r9,r9
  0x0000000002512bcb: cmovne r11d,r10d
  0x0000000002512bcf: and    eax,r11d           ;*iand
  0x0000000002512bd2: add    rsp,0x10
  0x0000000002512bd6: pop    rbp
  0x0000000002512bd7: test   DWORD PTR [rip+0xffffffffffc0d423],eax        # 0x0000000002120000
  0x0000000002512bdd: ret    
  0x0000000002512bde: xor    eax,eax
  0x0000000002512be0: jmp    0x0000000002512bbf
  .....

惊喜!由 Java 7 中的 JIT 编译器为该方法生成的汇编代码现在只有一个条件跳转,并且更短!然而,方法(你必须相信我,我不想弄乱结局!)仍然大致相同,有两个条件跳转和几个更少的指令,顶部。
看起来番石榴的工程师知道他们在做什么,毕竟!(如果他们试图优化Java 7执行时间,那就是;-)&&&

所以回到OP的最新问题:

这种使用 &(哪里 && 会更正常)是真正的优化吗?

恕我直言,答案是一样的,即使对于这个(非常!)特定的场景:这取决于你的JVM实现,你的编译器,你的CPU和你的输入数据


答案 2

对于这类问题,您应该运行一个微基准标记。我使用JMH进行此测试。

基准测试实现为

// boolean logical AND
bh.consume(value >= x & y <= value);

// conditional AND
bh.consume(value >= x && y <= value);

// bitwise OR, as suggested by Joop Eggen
bh.consume(((value - x) | (y - value)) >= 0)

根据基准名称使用 的值。value, x and y

吞吐量基准测试的结果(五次预热和十次测量迭代)为:

Benchmark                                 Mode  Cnt    Score    Error   Units
Benchmark.isBooleanANDBelowRange          thrpt   10  386.086 ▒ 17.383  ops/us
Benchmark.isBooleanANDInRange             thrpt   10  387.240 ▒  7.657  ops/us
Benchmark.isBooleanANDOverRange           thrpt   10  381.847 ▒ 15.295  ops/us
Benchmark.isBitwiseORBelowRange           thrpt   10  384.877 ▒ 11.766  ops/us
Benchmark.isBitwiseORInRange              thrpt   10  380.743 ▒ 15.042  ops/us
Benchmark.isBitwiseOROverRange            thrpt   10  383.524 ▒ 16.911  ops/us
Benchmark.isConditionalANDBelowRange      thrpt   10  385.190 ▒ 19.600  ops/us
Benchmark.isConditionalANDInRange         thrpt   10  384.094 ▒ 15.417  ops/us
Benchmark.isConditionalANDOverRange       thrpt   10  380.913 ▒  5.537  ops/us

结果对于评估本身来说并没有太大的不同。只要没有发现对该段代码的性能影响,我就不会尝试对其进行优化。根据代码中的位置,热点编译器可能会决定进行一些优化。上述基准可能未涵盖这一点。

一些参考:

布尔逻辑 AND - 结果值是如果两个操作数值都为 ;否则,结果是
条件的 AND - 与 类似,但仅当其左侧操作数的值是
按位 OR 时,才计算其右操作数 - 结果值是操作数值的按位包含 ORtruetruefalse&true


推荐