在Java中检测偶数的最有效方法是什么?

2022-09-03 17:08:18

确定一个数字甚至使用Java的最有效方法是什么,为什么?

是使用模数还是减法,还是其他一些我实际上没有想到的方式?

人们想象我可以确定这是做一个简单的测试类 - 我可以 - 但这真的不能解释为什么,对吧?

我不是在为一些更快的处理那么多项目的崇高目标做一些疯狂的性能调整。但我很好奇,作为常见的做法,一种方法是否应该优先于另一种方法。以同样的方式,我们不会使用代替,为什么使用当我们可以使用时?&&&%&


答案 1

如果检查由以下两种方法中的热点 7 生成的程序集:

public static boolean isEvenBit(int i) {
    return (i & 1) == 0;
}
public static boolean isEvenMod(int i) {
    return i % 2 == 0;
}

你会看到,虽然mod是优化的,基本上是按位的,但它有一些额外的指令,因为这两个操作不是严格等价的*。其他JVM可能会以不同的方式对其进行优化。该程序集发布在下面以供参考。and

我还运行了一个微基准测试,证实了我们的观察结果:isEventBit稍微快一点(但两者都运行在大约2纳秒内,所以可能不会对整个典型程序产生太大的影响):

Benchmark                     Mode  Samples  Score   Error  Units
c.a.p.SO16969220.isEvenBit    avgt       10  1.869 ± 0.069  ns/op
c.a.p.SO16969220.isEvenMod    avgt       10  2.554 ± 0.142  ns/op

是易比特

  # {method} 'isEvenBit' '(I)Z' in 'javaapplication4/Test1'
  # parm0:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000026c2580: sub    rsp,0x18
  0x00000000026c2587: mov    QWORD PTR [rsp+0x10],rbp  ;*synchronization entry
                                                ; - javaapplication4.Test1::isEvenBit@-1 (line 66)
  0x00000000026c258c: and    edx,0x1
  0x00000000026c258f: mov    eax,edx
  0x00000000026c2591: xor    eax,0x1            ;*ireturn
                                                ; - javaapplication4.Test1::isEvenBit@11 (line 66)
  0x00000000026c2594: add    rsp,0x10
  0x00000000026c2598: pop    rbp
  0x00000000026c2599: test   DWORD PTR [rip+0xfffffffffdb6da61],eax        # 0x0000000000230000
                                                ;   {poll_return}
  0x00000000026c259f: ret    

isEvenMod

  # {method} 'isEvenMod' '(I)Z' in 'javaapplication4/Test1'
  # parm0:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000026c2780: sub    rsp,0x18
  0x00000000026c2787: mov    QWORD PTR [rsp+0x10],rbp  ;*synchronization entry
                                                ; - javaapplication4.Test1::isEvenMod@-1 (line 63)
  0x00000000026c278c: mov    r10d,edx
  0x00000000026c278f: and    r10d,0x1           ;*irem
                                                ; - javaapplication4.Test1::isEvenMod@2 (line 63)
  0x00000000026c2793: mov    r11d,r10d
  0x00000000026c2796: neg    r11d
  0x00000000026c2799: test   edx,edx
  0x00000000026c279b: cmovl  r10d,r11d
  0x00000000026c279f: test   r10d,r10d
  0x00000000026c27a2: setne  al
  0x00000000026c27a5: movzx  eax,al
  0x00000000026c27a8: xor    eax,0x1            ;*ireturn
                                                ; - javaapplication4.Test1::isEvenMod@11 (line 63)
  0x00000000026c27ab: add    rsp,0x10
  0x00000000026c27af: pop    rbp
  0x00000000026c27b0: test   DWORD PTR [rip+0xfffffffffdb6d84a],eax        # 0x0000000000230000
                                                ;   {poll_return}
  0x00000000026c27b6: ret    

*正如评论中指出的那样,%并不是真正的模数;这是其余的。所以(i % 2)!= (i & 1)如果我<0isEvenMod代码中的额外指令将结果的符号设置为i的符号(然后将其与零进行比较,因此浪费了精力)。


答案 2

另一种方法是运行微基准测试并分析每个变体所花费的时间。结果如下:

Benchmark    Mean    Units    Time vs. baseline
baseline     10.330  nsec/op     0.000
bitAnd       12.075  nsec/op     1.745
bitShift     12.309  nsec/op     1.979
modulo       12.309  nsec/op     4.529

(基线是一个只返回的方法i == 0)

结论:

  • i & 1----->大约需要1.75ns
  • i << 31-->大约需要2.00ns
  • i % 2----->大约需要4.50ns

换句话说,比 慢 2 倍。i % 2i & 1

注意:使用jmh完成基准测试。基线很高,因为我生成随机数以确保方法没有被优化。测试在 i7 @ 2.8GHz(即一个周期 = 0.35ns)上运行,热点为 7。


推荐