模运算符和按位AND的性能比较

Lau*_*Fai 4 java performance bit-manipulation jmh

我正在努力确定32位整数是偶数还是奇数.我设置了两种方法:

模数(%)方法

int r = (i % 2);
Run Code Online (Sandbox Code Playgroud)

按位(&)方法

int r = (i & 0x1);
Run Code Online (Sandbox Code Playgroud)

两种方法都成功运作.所以我每行运行15000次来测试性能.

结果:

模数(%)方法(源代码)

意思是141.5801887ns | SD 270.0700275ns

按位(&)方法(源代码)

意思是141.2504ns | SD 193.6351007ns

问题:

为什么按位(&)比除法(%)更稳定?

JVM是否根据此处使用AND(&)优化模数(%)?

St.*_*rio 10

让我们尝试用JMH重现.

@Benchmark
@Measurement(timeUnit = TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public int first() throws IOException {
    return i % 2;
}

@Benchmark
@Measurement(timeUnit = TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public int second() throws IOException {
    return i & 0x1;
}
Run Code Online (Sandbox Code Playgroud)

好的,它是可重复的.该first比稍慢second.现在让我们弄清楚原因.运行它-prof perfnorm:

Benchmark                                 Mode  Cnt   Score    Error  Units
MyBenchmark.first                         avgt   50   2.674 ±  0.028  ns/op
MyBenchmark.first:CPI                     avgt   10   0.301 ±  0.002   #/op
MyBenchmark.first:L1-dcache-load-misses   avgt   10   0.001 ±  0.001   #/op
MyBenchmark.first:L1-dcache-loads         avgt   10  11.011 ±  0.146   #/op
MyBenchmark.first:L1-dcache-stores        avgt   10   3.011 ±  0.034   #/op
MyBenchmark.first:L1-icache-load-misses   avgt   10  ? 10?³            #/op
MyBenchmark.first:LLC-load-misses         avgt   10  ? 10??            #/op
MyBenchmark.first:LLC-loads               avgt   10  ? 10??            #/op
MyBenchmark.first:LLC-store-misses        avgt   10  ? 10??            #/op
MyBenchmark.first:LLC-stores              avgt   10  ? 10??            #/op
MyBenchmark.first:branch-misses           avgt   10  ? 10??            #/op
MyBenchmark.first:branches                avgt   10   4.006 ±  0.054   #/op
MyBenchmark.first:cycles                  avgt   10   9.322 ±  0.113   #/op
MyBenchmark.first:dTLB-load-misses        avgt   10  ? 10??            #/op
MyBenchmark.first:dTLB-loads              avgt   10  10.939 ±  0.175   #/op
MyBenchmark.first:dTLB-store-misses       avgt   10  ? 10??            #/op
MyBenchmark.first:dTLB-stores             avgt   10   2.991 ±  0.045   #/op
MyBenchmark.first:iTLB-load-misses        avgt   10  ? 10??            #/op
MyBenchmark.first:iTLB-loads              avgt   10  ? 10??            #/op
MyBenchmark.first:instructions            avgt   10  30.991 ±  0.427   #/op
MyBenchmark.second                        avgt   50   2.263 ±  0.015  ns/op
MyBenchmark.second:CPI                    avgt   10   0.320 ±  0.001   #/op
MyBenchmark.second:L1-dcache-load-misses  avgt   10   0.001 ±  0.001   #/op
MyBenchmark.second:L1-dcache-loads        avgt   10  11.045 ±  0.152   #/op
MyBenchmark.second:L1-dcache-stores       avgt   10   3.014 ±  0.032   #/op
MyBenchmark.second:L1-icache-load-misses  avgt   10  ? 10?³            #/op
MyBenchmark.second:LLC-load-misses        avgt   10  ? 10??            #/op
MyBenchmark.second:LLC-loads              avgt   10  ? 10??            #/op
MyBenchmark.second:LLC-store-misses       avgt   10  ? 10??            #/op
MyBenchmark.second:LLC-stores             avgt   10  ? 10??            #/op
MyBenchmark.second:branch-misses          avgt   10  ? 10??            #/op
MyBenchmark.second:branches               avgt   10   4.014 ±  0.045   #/op
MyBenchmark.second:cycles                 avgt   10   8.024 ±  0.098   #/op
MyBenchmark.second:dTLB-load-misses       avgt   10  ? 10??            #/op
MyBenchmark.second:dTLB-loads             avgt   10  10.989 ±  0.161   #/op
MyBenchmark.second:dTLB-store-misses      avgt   10  ? 10??            #/op
MyBenchmark.second:dTLB-stores            avgt   10   3.004 ±  0.042   #/op
MyBenchmark.second:iTLB-load-misses       avgt   10  ? 10??            #/op
MyBenchmark.second:iTLB-loads             avgt   10  ? 10??            #/op
MyBenchmark.second:instructions           avgt   10  25.076 ±  0.296   #/op
Run Code Online (Sandbox Code Playgroud)

注意周期和指令的差异.现在这很明显了.该first很关心的迹象,但second不(只按位与).要确保这是原因,请查看程序集片段:

第一:

0x00007f91111f8355: mov     0xc(%r10),%r11d   ;*getfield i
0x00007f91111f8359: mov     %r11d,%edx
0x00007f91111f835c: and     $0x1,%edx
0x00007f91111f835f: mov     %edx,%r10d
0x00007f6bd120a6e2: neg     %r10d
0x00007f6bd120a6e5: test    %r11d,%r11d
0x00007f6bd120a6e8: cmovl   %r10d,%edx       
Run Code Online (Sandbox Code Playgroud)

第二:

0x00007ff36cbda580: mov     $0x1,%edx
0x00007ff36cbda585: mov     0x40(%rsp),%r10
0x00007ff36cbda58a: and     0xc(%r10),%edx  
Run Code Online (Sandbox Code Playgroud)

  • 我在这里错过了一个解释,比如"模数是一个非常慢的操作,但是JVM优化`i%2`为`i> 0?i&1: - (i&1)`. (4认同)