Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance difference between Java direct array index access vs. for loop access

I was experimenting with predicates. I tried to implement the predicate for serializing issues in distributed systems. I wrote a simple example where the test function just returns true. I was measuring the overhead, and I stumbled upon this interesting problem. Accessing array in for loop is 10 times slower compared to direct access.

class Test {
    public boolean test(Object o) { return true; }
}

long count = 1000000000l;
Test[] test = new Test[3];
test[0] = new Test();
test[1] = new Test();
test[2] = new Test();
long milliseconds = System.currentTimeMillis();
for(int i = 0; i < count; i++){
    boolean result = true;
    Object object = new Object();
    for(int j = 0; j < test.length; j++){
        result = result && test[j].test(object);
    }
}
System.out.println((System.currentTimeMillis() - milliseconds));

However, the following code is almost 10 times faster. What can be the reason?

milliseconds = System.currentTimeMillis();
for(int i=0 ; i < count; i++) {
    Object object = new Object();
    boolean result = test[0].test(object) && test[1].test(object) && test[2].test(object);
}
System.out.println((System.currentTimeMillis() - milliseconds));

Benchmark results on my i5.

  • 4567 msec for for loop access

  • 297 msec for direct access

like image 787
Ozan Deniz Avatar asked Sep 19 '26 18:09

Ozan Deniz


2 Answers

Due to the predictable result of test(Object o) the compiler is able to optimize the second piece of code quite effectively. The second loop in the first piece of code makes this optimization impossible.

Compare the result with the following Test class:

static class Test {
    public boolean test(Object o) {
        return Math.random() > 0.5;
    }
}  

... and the loops:

    long count = 100000000l;
    Test[] test = new Test[3];
    test[0] = new Test();
    test[1] = new Test();
    test[2] = new Test();

    long milliseconds = System.currentTimeMillis();

    for(int i = 0; i < count; i++){
        boolean result = true;
        Object object = new Object();
        for(int j = 0; j < test.length; j++){
            result = result && test[j].test(object);
        }
    }

    System.out.println((System.currentTimeMillis() - milliseconds));
    milliseconds = System.currentTimeMillis();

    for(int i=0 ; i < count; i++) {
        Object object = new Object();
        boolean result = test[0].test(object) && test[1].test(object) && test[2].test(object);
    }

    System.out.println((System.currentTimeMillis() - milliseconds));

Now both loops require almost the same time:

run:
3759
3368
BUILD SUCCESSFUL (total time: 7 seconds)

p.s.: check out this article for more about JIT compiler optimizations.

like image 66
Trinimon Avatar answered Sep 22 '26 06:09

Trinimon


You are committing almost every basic mistake you can make with a microbenchmark.

  • You don't ensure code cannot be optimized away by making sure to actually use the calculations result.
  • Your two code branches have subtly but decidedly different logic (as pointed out variant two will always short-circuit). The second case is easier to optimize for the JIT due to test() returning a constant.
  • You did not warm up the code, inviting JIT optimization time being included somewhere into the execution time
  • Your testing code is not accounting for execution order of test cases exerting an influence on the test results. Its not fair to run case 1, then case 2 with the same data and objects. The JIT will by the time case 2 runs have optimized the test method and collected runtime statistics about its behavior (at the expense of case 1's execution time).
like image 30
Durandal Avatar answered Sep 22 '26 08:09

Durandal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!