0

When executed once, code 1 is faster than code 2. The difference is quite significant.

But, when these codes are executed inside for loop (50 million times, see code 1 inside for loop and code 2 inside for loop) code 1 has the worst performance.

Is it possible to make code 1 perform better inside for loop?

Another question: is there another way to do these calculations inside for loop with a better performance?

    int [] arr1 = new int[50000000];
    for (int i = 0; i < 50000000; i++) {
        arr1[i] = i + 1000;
    }
    
    //code 1:
    long startTime1 = System.nanoTime();
    int som1 = 426;
    double a1 = (double) ((((double) Math.round((double) Math.pow((double) arr1[arr1.length - 1] / (double) som1, 2)) - 1) / 50 % 1) * 50) + 1;
    int a2 = (int) Math.round(a1);
    long estimatedTime1 = System.nanoTime() - startTime1;
    System.out.println("Code 1: " + estimatedTime1);
    
    //code 2:
    long startTime2 = System.nanoTime();
    int som2 = 426;
    double a3 = (double) arr1[arr1.length - 1] / (double) som2;
    double a4 = (double) (Math.round(a3 * a3) - 1);
    double a5 = (double) a4 / 50;
    double a6 = (double) Math.floor(a5);
    double a7 = (double) ((a5 - a6) * 50) + 1;
    int a8 = (int) Math.round(a7);
    long estimatedTime2 = System.nanoTime() - startTime2;
    System.out.println("Code 2: " + estimatedTime2);
    
    //code 1 inside for loop:
    long startTime3 = System.nanoTime();
    int som3 = 426;
    for (int j1 = 0; j1 < arr1.length; j1++) {
        double a9 = (double) ((((double) Math.round((double) Math.pow((double) arr1[j1] / (double) som1, 2)) - 1) / 50 % 1) * 50) + 1;
        int a10 = (int) Math.round(a9);
    }
    long estimatedTime3 = System.nanoTime() - startTime3;
    System.out.println("Code 1 inside for loop: " + estimatedTime3);
    
    //code 2 inside for loop:
    long startTime4 = System.nanoTime();
    int som4 = 426;
    for (int j2 = 0; j2 < arr1.length; j2++) {
        double a11 = (double) arr1[j2] / (double) som4;
        double a12 = (double) (Math.round(a11 * a11) - 1);
        double a13 = (double) a12 / 50;
        double a14 = (double) Math.floor(a13);
        double a15 = (double) ((a13 - a14) * 50) + 1;
        int a16 = (int) Math.round(a15);
    }
    long estimatedTime4 = System.nanoTime() - startTime4;
    System.out.println("Code 2 inside for loop: " + estimatedTime4);

Edit : As explained by @Sweeper, code 1 is slower in both cases. As I am trying to improve the performance of this code inside for loop, I would like to ask if there is a faster way to do these calculations inside for loop.

JOAO12
  • 45
  • 7
  • 1
    Are you sure your assumptions are correct? The execution time for a single iteration is likely to be disproprtionatley affected by other system activities (I/O, interrupt handling, task switching, etc.). These effects will be averaged out by the longer loops. – Tangentially Perpendicular Feb 17 '23 at 02:09
  • When executed once, code 1 is almost 10 times faster than code 2. But in for loop, code 1 takes almost 2 times more time than code 2. I am trying to get better performance in for loop. – JOAO12 Feb 17 '23 at 02:21
  • 3
    Try measuring "code 1 just once" and "code 2 just once" after you measure them in loops. I got different results that says code 1 is slower in both cases. This suggests that this is probably due to the VM not being warmed up at first. In any case, you should use [JMH](https://github.com/openjdk/jmh) for benchmarking. JMH's results also says that code 1 is slower in both cases, by the way. – Sweeper Feb 17 '23 at 03:01
  • See also: https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – Sweeper Feb 17 '23 at 03:01
  • It's very likely that your measurements are just straight-up lying to you. – Louis Wasserman Feb 17 '23 at 05:40
  • @Sweeper, I followed your instructions and I got the same result: code 1 is slower in both cases. As I am trying to improve the performance of this code inside for loop, I would like to ask you if there is a way to make code 2 better inside for loop? – JOAO12 Feb 17 '23 at 13:30
  • 1. Always use JMH to compare precisely. 2. Math.pow and % are relatively expensive operations. I bet they are the cause. Another cause may be the cache misses, but in both cases you have the same pattern of memory access. 3. To go deeply and to be sure you may review JMH with perfasm output (with enabled events-Djmh.perfasm.events=cycles,cache-misses) – AnatolyG Feb 19 '23 at 14:24

1 Answers1

2

You assumption that code1 was faster than code2 is wrong, as already explained.

You can read about common benchmark pitfalls in How do I write a correct micro-benchmark in Java?

For example, don’t put the different variants into the same method, better, do not even run them within the same JVM. Further, take care to use the result value and run the methods multiple times, to see the impact of first time initialization overhead and the JIT compiler and optimizer.

Generally, Math.pow(x, 2) is expected to be more expensive than x * x, unless the optimizer recognizes that you are using a constant argument. In the best case, the optimizer replaces Math.pow(x, 2) by x * x but we won’t get better performance than x * x.


But the two variants have another difference, which even produces different results.

  • The first variant uses … / 50 % 1 * 50 which is the same as … % 50.

  • The second variant uses (… / 50 - Math.floor(… / 50)) * 50
    which is the same as Math.floorMod(…, 50).

Both produce the same result for positive numbers but have an entirely different behavior for negative numbers.

  1. -60 % 50 == -10

  2. Math.floorMod(-60, 50) == 40

You should decide which behavior you want. Then, as shown above, there is a simpler alternative in either case.

  • For the first variant

    double temp = array[index] / (double)parameter;
    int result = (int)((Math.round(temp * temp) - 1) % 50) + 1;
    
  • For the second variant

    double temp = array[index] / (double) parameter;
    int result = Math.floorMod(Math.round(temp * temp) - 1, 50) + 1;
    

parameter is the int variable you initialized with 426

In either case I expect the simpler variant to be faster than your original code.


Note that Math.floorMod(long, int) requires Java 9. If you try to compile this code with Java 8, it will use Math.floorMod(long, long) and require you to insert a type cast to int for the final result. The result value will still be the same.

Holger
  • 285,553
  • 42
  • 434
  • 765