在Java中,可以比&&快吗?

Sus*_*anW 70 java performance processing-efficiency microbenchmark branch-prediction

在这段代码中:

if (value >= x && value <= y) {
Run Code Online (Sandbox Code Playgroud)

什么时候value >= xvalue <= y没有特定模式的情况一样真假,使用&运算符会比使用更快&&吗?

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

不过,虽然一个>=<=运营商将使用一个简单的比较指令时,&&必须包括一个分支,该分支是易受分支预测失败 -按本非常著名的问题:为什么快处理有序数组不是一个排序的数组?

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

笔记:

  • 很明显,如果代码看起来如此,我的问题的答案就是否定:if(value >= x && verySlowFunction()).我专注于"足够简单"的RHS表达.
  • 无论如何,那里有一个条件分支(if声明).我无法向自己证明这是无关紧要的,而且替代配方可能是更好的例子,比如boolean b = value >= x && value <= y;
  • 这一切都落入了可怕的微观优化世界.是的,我知道:-) ......虽然很有趣?

更新 只是为了解释为什么我感兴趣:我一直在盯着马丁汤普森在他的机械同情博客上撰写的系统,在他来到并谈到 Aeron之后.其中一个关键信息是我们的硬件中包含了所有这些神奇的东西,我们的软件开发人员不幸地无法利用它.别担心,我不打算在我的所有代码上使用///// :-) ...但是这个网站上有很多关于通过删除分支来改进分支预测的问题,并且它发生了对我来说,条件布尔运算符是测试条件的核心.

当然,@ StephenC提出了一个奇妙的观点,即将代码弯曲成奇怪的形状可以使JIT更容易发现常见的优化 - 如果不是现在,那么将来也是如此.并且上面提到的非常着名的问题是特殊的,因为它推动预测复杂性远远超出实际优化.

我非常清楚,在大多数(或几乎所有)情况下,这&&是最清晰,最简单,最快速,最好的事情 - 尽管我非常感谢那些发布了答案的人们!我真的很想知道在任何人的经历中是否有任何案例的答案"可以&更快?" 可能是的 ......

更新2: (解决建议,这个问题过于笼统,我不想作出重大改变这个问题,因为它可能会损害一些下面的答案,这是卓越品质的.!)也许在野外的例子被称为对于; 这是来自Guava LongMath类(非常感谢@maaartinus发现这个):

public static boolean isPowerOfTwo(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}
Run Code Online (Sandbox Code Playgroud)

先看到了&吗?如果检查链接,则会调用下一个方法,该方法lessThanBranchFree(...)暗示我们处于避免分支区域 - 而番石榴确实被广泛使用:保存的每个周期都会导致海平面明显下降.所以让我们用这样的方式提出问题:这种使用&(哪里&&更正常)是一种真正的优化?

wal*_*len 73

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

编辑:最后为AMD64添加了生成的汇编代码.看看一些有趣的笔记.
编辑2(re:OP的"更新2"):也为Guava的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;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,它们完全相同,除了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
Run Code Online (Sandbox Code Playgroud)

AndSC(&&)方法生成2个条件跳转,如所预期:

  1. 它加载valuex压入堆栈,并跳转到L1如果value是较低的.否则它会继续运行下一行.
  2. 它加载valuey压入堆栈,并跳转到L1还,如果value是更大的.否则它会继续运行下一行.
  3. 这恰好是return true两个跳跃都没有发生的情况.
  4. 然后我们将标记为L1的线条作为a return false.

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

  1. 它加载valuex压入堆栈,并跳转到L1如果value是较低的.因为现在需要保存结果以将其与AND的其他部分进行比较,因此它必须执行"保存true"或"保存false",它不能同时使用相同的指令.
  2. 它加载valuey压入堆栈,并跳转到L1如果value是较大的.根据比较结果,它需要再次保存truefalse两个不同的行.
  3. 现在两个比较都已完成,代码实际执行AND操作 - 如果两者都为真,则跳转(第三次)返回true; 否则它继续执行到下一行返回false.

(初步)结论

虽然我对Java字节码没有那么多经验而且我可能忽略了一些东西,但在我看来,它&实际上会比每种情况都要糟糕&&:它会生成更多的指令来执行,包括更多的条件跳转来预测并可能在.

正如其他人提出的那样,重写代码以替代与算术运算的比较可能是一种做出&更好选择的方法,但代价是使代码更不清晰.
恕我直言,99%的情况都不值得麻烦(尽管如此,对于需要进行极其优化的1%循环来说,这可能非常值得).

编辑:AMD64程序集

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

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

AndSC使用默认选项的方法

  # {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   
Run Code Online (Sandbox Code Playgroud)

AndSC-XX:PrintAssemblyOptions=intel选项的方法

  # {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
Run Code Online (Sandbox Code Playgroud)

AndNonSC使用默认选项的方法

  # {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   
Run Code Online (Sandbox Code Playgroud)

AndNonSC-XX:PrintAssemblyOptions=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
Run Code Online (Sandbox Code Playgroud)
  • 首先,生成的ASM代码根据我们选择默认的AT&T语法还是Intel语法而有所不同.
  • 使用AT&T语法:
    • 对于该方法,ASM代码实际上更长AndSC,每个字节码都IF_ICMP*转换为两个汇编跳转指令,总共有4个条件跳转.
    • 同时,对于该AndNonSC方法,编译器生成更直接的代码,其中每个字节代码IF_ICMP*仅转换为一个汇编跳转指令,保持3个条件跳转的原始计数.
  • 使用Intel语法:
    • ASM代码AndSC更短,只有2个条件跳转(不计算最后的非条件jmp).实际上它只是两个CMP,两个JL/E和一个XOR/MOV,具体取决于结果.
    • ASM代码AndNonSC现在比AndSC一个更长!但是,它只有1个条件跳转(第一次比较),使用寄存器直接比较第一个结果和第二个结果,而不再跳转.

ASM代码分析后的结论

  • 在AMD64机器语言级别,&运营商似乎生成具有较少条件跳转的ASM代码,这对于高预测失败率(value例如随机s)可能更好.
  • 另一方面,&&运算符似乎生成带有较少指令的ASM代码(-XX:PrintAssemblyOptions=intel无论如何都有选项),这对于具有预测友好输入的真正长循环可能更好,其中每次比较的CPU周期数较少可以产生差异长期来说.

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


附录:番石榴的isPowerOfTwo方法

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

public static boolean isPowerOfTwo(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}
Run Code Online (Sandbox Code Playgroud)

引用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;
}
Run Code Online (Sandbox Code Playgroud)

英特尔针对Guava版本的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    
Run Code Online (Sandbox Code Playgroud)

英特尔的asm代码&&版本

  # {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    
Run Code Online (Sandbox Code Playgroud)

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

所以一切都指向Guava的&方法效率低于更"自然"的&&版本.

......或者是吗?

如前所述,我使用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)
Run Code Online (Sandbox Code Playgroud)

但是,如果我切换到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
  .....
Run Code Online (Sandbox Code Playgroud)

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

回到OP的最新问题:

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

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

  • 虽然它可能是"与ASM最接近的东西",但它并不足以让您得出任何合乎逻辑的结论.简而言之,在代码被JIT编译之后,JVM不执行字节码. (9认同)
  • 那么,Java字节码是最接近ASM的东西,然后才会涉及到每个操作系统和CPU的细节.当然,IBM`javac`可能会输出与官方Oracle或OpenJDK不同的代码......当然,X86机器中的机器代码可能与PowerPC AIX系统或许多智能手机中使用的Snapdragon CPU不同 - 每个平台都有自己的编译器和优化.但是在这样一个简单的例子中,我怀疑从一个CPU到另一个CPU的差异将比2比3字节码条件跳转产生更大的差异. (8认同)
  • @Riley是的,但我可以联系,所以没问题:)请允许我引用英特尔的官方[英特尔®64和IA-32架构软件开发人员手册](http://www.intel.com/content/dam/ www/public/us/en/documents/manuals/64-ia-32-architecturalures-software-developer-vol-1-manual.pdf):"_**5.1.7控制转移指令**_ _控制转移指令提供跳转,**条件跳转**,循环,以及调用和返回操作来控制程序流._" (2认同)
  • 嗯,我认为这是一个很棒的答案。Java8 中可能有一些微妙之处,这可能使它在 HotSpot 魔法或其他东西的基础上应用进一步的优化。在这种情况下,可能会产生一个新问题......同时,很好!非常感谢! (2认同)

Sub*_*mal 23

对于那些问题,你应该运行一个微基准测试.我用 JMH进行了这个测试.

基准测试实现为

// boolean logical AND
bh.consume(value >= x & y <= value);
Run Code Online (Sandbox Code Playgroud)

// conditional AND
bh.consume(value >= x && y <= value);
Run Code Online (Sandbox Code Playgroud)

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

具有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
Run Code Online (Sandbox Code Playgroud)

结果与评估本身没有什么不同.只要在那段代码上没有发现性能影响,我就不会尝试对其进行优化.根据代码中的位置,热点编译器可能会决定进行一些优化.上述基准可能未涵盖哪些内容.

一些参考:

boolean logical AND - true如果两个操作数值都是true,则结果值为; 否则,结果是false
条件AND - 就像&,但只有当其左侧操作数的值为true
按位OR时才计算其右侧操作数- 结果值是操作数值的按位包含OR

  • 这是迄今为止最好的基准,但它也有缺陷:)黑洞需要的时间远远超过&&或者所以你基本上测量的是黑洞性能:)尝试使用像消费这样的东西(a&b&c 7 d&f &g ....&z); (4认同)

Ste*_*n C 13

我将从另一个角度来看待这个问题.

考虑这两个代码片段,

  if (value >= x && value <= y) {
Run Code Online (Sandbox Code Playgroud)

  if (value >= x & value <= y) {
Run Code Online (Sandbox Code Playgroud)

如果我们假设value,x,y有一个基本类型,那么这两个(部分)语句可以得到同样的结果对于所有可能的输入值.(如果涉及包装类型,那么它们并不完全等效,因为对它的隐式null测试y可能在&版本而不是&&版本中失败.)

如果JIT编译器做得很好,它的优化器将能够推断出这两个语句做同样的事情:

  • 如果一个人是可以预见比其他快,那么就应该可以使用更快的版本... 在JIT编译的代码.

  • 如果没有,那么在源代码级别使用哪个版本并不重要.

  • 由于JIT编译器在编译之前收集路径统计信息,因此它可能有更多关于程序员(!)的执行特性的信息.

  • 如果当前一代JIT编译器(在任何给定平台上)没有足够好地进行优化来处理这个问题,那么下一代就可以做到......取决于经验证据是否指出这是一个值得优化的模式.

  • 事实上,如果你写你在这个优化的方式Java代码中,有一个机会,通过采摘的代码更加"隐晦"的版本,你可能会抑制当前或未来的JIT编译器的优化能力.

简而言之,我认为你不应该在源代码级别进行这种微优化.如果你接受这个论点1,并按照它的逻辑结论,那么哪个版本更快的问题是......模拟2.

1 - 我并不认为这是一个接近证据的地方.

2 - 除非你是真正编写Java JIT编译器的微小社区之一...


"非常着名的问题"在两个方面很有意思:

  • 一方面,这是一个例子,其中产生差异所需的优化类型超出了JIT编译器的能力.

  • 另一方面,对数组进行排序不一定是正确的事情......只是因为可以更快地处理排序的数组.排序阵列的成本很可能(远远大于)节省.

  • 关于抑制未来优化的观点是非常好的! - 故意将'&'置于某种状态将等于"未能明确表达意图以欺骗系统",并且_当你骗你的电脑时,它会报复...... (3认同)

Luk*_*aia 6

使用&或者&&仍然需要评估条件,因此不太可能节省任何处理时间 - 考虑到您只需要评估一个表达式时,它甚至可以添加它.

如果在一些非常罕见的情况下使用&过度&&来节省一个纳秒是没有意义的,那么你已经浪费了更多的时间来考虑差异而不是使用&过来节省的时间&&.

编辑

我好奇,决定跑一些基准.

我上了这堂课:

public class Main {

    static int x = 22, y = 48;

    public static void main(String[] args) {
        runWithOneAnd(30);
        runWithTwoAnds(30);
    }

    static void runWithOneAnd(int value){
        if(value >= x & value <= y){

        }
    }

    static void runWithTwoAnds(int value){
        if(value >= x && value <= y){

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用NetBeans运行一些分析测试.我没有使用任何打印语句来节省处理时间,只知道两个评估true.

第一次测试:

第一次性能测试

第二次测试:

第二次剖析测试

第三次测试:

第三次剖析测试

正如您在分析测试中所看到的,&与使用两个测试相比,仅使用一个实际上需要花费2-3倍的时间&&.这确实发生了一些奇怪的事情,因为我确实期望只有一个更好的表现&.

我不是100%肯定为什么.在这两种情况下,两个表达式都必须进行评估,因为两者都是真的.我怀疑JVM在幕后进行了一些特殊的优化以加快速度.

故事的道德:惯例是好的,过早的优化是坏的.


编辑2

我用@ SvetlinZarev的评论和其他一些改进来重新编写基准代码.以下是修改后的基准代码:

public class Main {

    static int x = 22, y = 48;

    public static void main(String[] args) {
        oneAndBothTrue();
        oneAndOneTrue();
        oneAndBothFalse();
        twoAndsBothTrue();
        twoAndsOneTrue();
        twoAndsBothFalse();
        System.out.println(b);
    }

    static void oneAndBothTrue() {
        int value = 30;
        for (int i = 0; i < 2000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void oneAndOneTrue() {
        int value = 60;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void oneAndBothFalse() {
        int value = 100;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void twoAndsBothTrue() {
        int value = 30;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void twoAndsOneTrue() {
        int value = 60;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void twoAndsBothFalse() {
        int value = 100;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    //I wanted to avoid print statements here as they can
    //affect the benchmark results. 
    static StringBuilder b = new StringBuilder();
    static int times = 0;

    static void doSomething(){
        times++;
        b.append("I have run ").append(times).append(" times \n");
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是性能测试:

测试1:

在此输入图像描述

测试2:

在此输入图像描述

测试3:

在此输入图像描述

这也考虑了不同的值和不同的条件.

&当两个条件都为真时,使用一个运行需要更多时间,大约60%或2毫秒更多时间.当其中一个或两个条件都为假时,则一个&运行得更快,但它只运行约0.30-0.50毫秒.因此&&&大多数情况下运行速度更快,但性能差异仍然可以忽略不计.

  • 你的微观基准是完全有缺陷的.JIT将优化那些空的for循环,更不用说在代码中单个执行方法永远不会给出任何有意义的结果. (5认同)
  • 微基准测试的唯一正确方法是使用像JMH这样的工具. (4认同)