1

I am experiencing some quite strange performance behaviour when evaluating some very specific if conditions.

Basically I have found that I can construct two conditions a and b such that if(a) is 10 times faster than if(a && b) even if a is always false, which seems to clash directly with the fact that Java should do lazy evaluation of conditions.

The case occurs when I have a to be someDate.after(startdate) and b to be someDate.before(enddate) - i.e a fairly standard range condition.

Below I've attached the code I use to show this particular issue. I've run the code on Windows 7 with Java 7, and one of my colleagues have run it on Windows 7 with java 6,7 and 8 - all with the same result.

Can anyone explain why this could happen?

import java.util.Date;

public class DateWeirdness {
public static void main(String[] args) {
    Date start = new Date();
    System.out.println("if(a) - a always false");
    System.out.println(timeBasic(2000000000, start, start.getTime() - 4000000000L));
    start = new Date();
    System.out.println("if(a && b) - a always false, a and b both date method");
    System.out.println(timeAdv(2000000000, start, start.getTime() - 4000000000L, new Date()));
    start = new Date();
    System.out.println("if(a && b) - a always false, b is condition on longs not date");
    System.out.println(timeAdv2(2000000000, start, start.getTime() - 4000000000L, new Date().getTime()));
    start = new Date();
    System.out.println("if(a) - a always false - condition on long");
    System.out.println(timeBasicL(2000000000, start.getTime(), start.getTime() - 4000000000L));
    start = new Date();
    System.out.println("if(a && b) - a always false, a and and b both conditions on long");
    System.out.println(timeAdvL(2000000000, start.getTime(), start.getTime() - 4000000000L, new Date().getTime()));
    start = new Date();
    System.out.println("if(a && b) - a always false, b always true");
    System.out.println(timeAdv(2000000000, start, start.getTime() - 4000000000L, new Date()));
    start = new Date();
    System.out.println("if(a && b) - both true");
    System.out.println(timeAdv(2000000000, start, start.getTime() + 1, new Date(start.getTime() + 4000000000L)));
    start = new Date();
    System.out.println("if(a && b) - a always true, b always false");
    System.out.println(timeAdv(2000000000, start, new Date(start.getTime() + 4000000001L).getTime(), new Date(start.getTime() + 4000000000L)));
}

private static int timeBasic(int size, Date start, long l) {
    long begin = System.currentTimeMillis();
    int c = 0;
    for (int i = 0; i < size; i++) {
        Date date = new Date(l++);
        if (start.before(date)) {
            c++;
        }
    }
    System.out.println(System.currentTimeMillis() - begin);
    return c;
}

private static int timeAdv(int size, Date start, long l, Date end) {
    long begin = System.currentTimeMillis();
    int c = 0;
    for (int i = 0; i < size; i++) {
        Date date = new Date(l++);
        if (start.before(date) && end.after(date)) {
            c++;
        }
    }
    System.out.println(System.currentTimeMillis() - begin);
    return c;
}

private static int timeAdv2(int size, Date start, long l, long end) {
    long begin = System.currentTimeMillis();
    int c = 0;
    for (int i = 0; i < size; i++) {
        Date date = new Date(l++);
        if (start.before(date) && end > l) {
            c++;
        }
    }
    System.out.println(System.currentTimeMillis() - begin);
    return c;
}

private static int timeBasicL(int size, long start, long l) {
    long begin = System.currentTimeMillis();
    int c = 0;
    for (int i = 0; i < size; i++) {
        l++;
        if (start < l) {
            c++;
        }
    }
    System.out.println(System.currentTimeMillis() - begin);
    return c;
}

private static int timeAdvL(int size, long start, long l, long end) {
    long begin = System.currentTimeMillis();
    int c = 0;
    for (int i = 0; i < size; i++) {
        l++;
        if (start < l && end > l) {
            c++;
        }
    }
    System.out.println(System.currentTimeMillis() - begin);
    return c;
}
}

When running it locally I get the following output. The interesting tests are the three first ones. 640 is the performance in milliseconds of doing if(a), 7079 is the performance of doing if(a && b), where a and b are as described above. 710 is the performance of doing if(a && b) where b is someDateAsLong < endDateAsLong.

if(a) - a always false
640
0
if(a && b) - a always false, a and b both date method
7079
0
if(a && b) - a always false, b is condition on longs not date
710
0
if(a) - a always false - condition on long
639
0
if(a && b) - a always false, a and and b both conditions on long
708
0
if(a && b) - a always false, b always true
6873
0
if(a && b) - both true
11995
2000000000
if(a && b) - a always true, b always false
13746
0
Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
  • 2
    Writing meaningful micro-benchmarks in Java is hard. Did you [read this post](http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java)? If not, then go back and *do that now*! It might go easier, if you use an existing framework [like caliper](https://code.google.com/p/caliper/). – Joachim Sauer Oct 14 '13 at 10:48
  • You would get better results with a profiling tool. If you are using eclipse try: http://www.eclipse.org/tptp/ – Salvatorelab Oct 14 '13 at 11:11
  • @TheBronx: I don't think a profiler is a substitute for writing a good micro-benchmark. – Joachim Sauer Oct 14 '13 at 12:04
  • @JoachimSauer with a profiler you can see what's happening under the hood (number of calls, methods consuming resources...). I think that would help too. Not in substitution of micro-bench, I think both are useful. – Salvatorelab Oct 14 '13 at 12:12

2 Answers2

2

When you run code which doesn't do anything useful, you are dependant on what the JIT can eliminate or not. In your case it appears the code is being eliminated.

In the fast case, each loop is taking 0.32 nano-seconds, which is far too short to be realistic. i.e. one clock cycle approx. The slower case is 3.6 ns which is more realistic but it could mean it is only mostly optimised away.

When you switch behaviours from always false to always true and visa versa, the code has to be re-optimised and you may be timing how lon it takes to detect this and replace the code, not how long it takes to actual execute.

I suggest you add an outer loop to run all the code in main() three times and you should see an even mroe dramatic difference.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
0

The JVM optimizes your code in a way that in the first case start.before(date) isn't evaluated at all (I think). Try running it with -Djava.compiler=NONE and see the results.

Dia
  • 271
  • 1
  • 13
  • You seem to be right, I ran with -Djava.compiler=NONE and then the difference in time went away. I'd assumed that the fact that I re-initialized the date inside the loop would be enough to ensure that it wasn't optimized, but clearly it wasn't. Thank you for your answer. – user2878443 Oct 14 '13 at 11:20
  • 1
    `-Djava.compiler=NONE` disables the compiler, so its results are absolutely meaningless for real production use. – Joachim Sauer Oct 14 '13 at 12:04
  • Yes, but also turns off optimization, so you can see that it is not a && operator problem. – Dia Oct 14 '13 at 14:03