Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance comparison of modulo operator and bitwise AND

I'm working to determine if an 32bit integer is even or odd. I have setup 2 approaches:

modulo(%) approach

int r = (i % 2);

bitwise(&) approach

int r = (i & 0x1);

Both approaches work successfully. So I run each line for 15000 times to test performance.

Result:

modulo(%) approach (source code)

mean 141.5801887ns | SD 270.0700275ns

bitwise(&) approach (source code)

mean 141.2504ns | SD 193.6351007ns

Questions:

Why is bitwise(&) more stable than division(%) ?

Does JVM optimize modulo(%) using AND(&) according to here?

like image 654
Lau Ka Fai Avatar asked Dec 13 '22 15:12

Lau Ka Fai


1 Answers

Let's try to reproduce with 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;
}

Okay, it is reproducable. The first is slightly slower than the second. Now let's figure out why. Run it with -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

Note the difference in cycles and instructions. And now that's kind of obvious. The first does care about the sign, but the second does not (just bitwise and). To make sure this is the reason take a look at the assembly fragment:

first:

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       

second:

0x00007ff36cbda580: mov     $0x1,%edx
0x00007ff36cbda585: mov     0x40(%rsp),%r10
0x00007ff36cbda58a: and     0xc(%r10),%edx  
like image 167
St.Antario Avatar answered Feb 22 '23 23:02

St.Antario