2

I have run the following program in different version of Java.

final double[] values = new double[10000];
final long start = System.currentTimeMillis();
double sum = 0;

for (int i = 0; i < values.length; i++)
    sum += Math.pow(values[i], 2);

final long elapsed = System.currentTimeMillis() - start;
System.out.println("Time elapse :: " + elapsed);

Java 7: output

Time elapse :: 1

Java 8: output

Time elapse :: 7

Why there an performance issue in Java 8 as compare to 7?

Ian2thedv
  • 2,691
  • 2
  • 26
  • 47
Ankush soni
  • 1,439
  • 1
  • 15
  • 30
  • Maybe your dataset is too small to obtain reliable results. Can you try on bigger sets? – Arnaud Denoyelle Aug 04 '15 at 09:35
  • Currently I am trying with 10000 double, I have tried with 1000000 double also but getting the same result. – Ankush soni Aug 04 '15 at 09:36
  • 4
    _Avoid Benchmarking Pitfalls on the JVM_ http://www.oracle.com/technetwork/articles/java/architect-benchmarking-2266277.html – dotvav Aug 04 '15 at 09:37
  • 1
    Why are you concerned that a single call to `pow` is going to take 0.0000007s instead of 0.0000001s? Will you be doing it so many times that it matters? :-) – paxdiablo Aug 04 '15 at 09:38
  • better use values[i] * values[i] as it is faster then the math.pow – Blaatz0r Aug 04 '15 at 09:39
  • 3
    Are you always computing the value of Math.pow(0, 2) or are you initializing your array with random values? – dotvav Aug 04 '15 at 09:42
  • 1
    Please read this: [How do I write a correct micro-benchmark in Java?](http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java) – Tom Aug 04 '15 at 09:53
  • @dotvav: Thanks, look like it is the known issue with Java 8 Math.pow function. – Ankush soni Aug 04 '15 at 09:57
  • 3
    The performance issues with `Math.pow` are explained [here](http://stackoverflow.com/a/29232947/3448419) – apangin Aug 04 '15 at 09:59
  • 1
    I contest the duplicate status of this question. OP's code does not prove it actually observes that particular anomaly, which is present only in some very specific (and outdated) versions of the JDK. – Marko Topolnik Aug 04 '15 at 10:16
  • @MarkoTopolnik Maybe it's not exactly the same issue, but the reason is definitely the same. The described behavior can be easily reproduced with JDK 7u25 vs JDK 8. Though OP's test is not a correct benchmark, it measures the interpreted performance of `Math.pow`, which was a Java method in JDK 7u25, but became a native method in JDK 8. – apangin Aug 04 '15 at 10:47
  • 1
    @apangin But my measurements reproduce OP's observations if I use insufficient warmup. So nobody can really tell. – Marko Topolnik Aug 04 '15 at 10:56
  • @MarkoTopolnik My explanation does not conflict with your observations. Insufficient warmup => execution in interpreted mode => slow native call in Java 8 vs. fast case in Java 7. With enough warmup the method gets JIT-compiled, and a native call is replaced with JVM intrinsic. – apangin Aug 04 '15 at 11:14
  • 1
    @apangin If you limit the explanation to behavior specific to interpreted code, it works. However, both this and the "duplicate" Q&A state/imply that certain releases of Java exhibit a performance regression for squaring with `pow`. We cannot tell whether OP is observing that particular regression or not. – Marko Topolnik Aug 04 '15 at 11:19
  • 1
    @apangin Studying your answer on the duplicate, and the JDK ticket you linked to there, it turns out that the regression happens only _after_ JIT compilation: it is not in the native method called by `StrictMath.pow()`, but rather in a different intrinsic introduced with JDK 7u40, which did not sport the special case for squares. So, no warmup -> no observation of the performance regression described in the duplicate. – Marko Topolnik Aug 04 '15 at 12:25

1 Answers1

5

First and foremost, your benchmark lacks any warmup. Here is some more realistic benchmark code:

public class Test
{

  public static void main(String[] args) {
    final Random rnd = new Random();
    for (int i = 0; i < 20; i++) {
      final double[] values = new double[10000];
      for (int j = 0; j < values.length; j++)
        values[j] = rnd.nextDouble();
      testPow(values);
    }
  }

  private static void testPow(double[] values) {
    final long start = System.nanoTime();
    double sum = 0;
    for (int i = 0; i < values.length; i++)
      sum += Math.pow(values[i], 2);
    final long elapsed = System.nanoTime() - start;
    System.out.println("Sum: " + sum + "; Time elapse :: " + TimeUnit.NANOSECONDS.toMillis(elapsed));
  }
}

I ran it on Java 8 and it gave 9 ms for one million elements. On Java 6 it gave 62 milliseconds. (sorry, I have no JDK 7 installed.)

On the other hand, if I don't initialize the array (like you didn't), then I get 0.9 ms for million elements, whereas Java 6 stays the same.

To reproduce your observation just take the results from the first run, then compare them to the improvement that happens later on. The smaller the array, the more repetition it takes to reach the JIT compiler's theshold for the number of iterations over the innermost loop before it triggers a given optimization.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436