Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Poor performance of Java's Math.pow(x, 2) when x = 0

Background

Having noticed that the execution of a java program I am working on was slower than expected, I decided to tinker with the area of code which I thought may be causing the issue - a call to Math.pow(x, 2) from within a for loop. Contrary to another questions on this site, a simple benchmark I created (code at end) found that replacing Math.pow(x, 2) with x*x actually sped the loop up by almost 70 times:

x*x: 5.139383ms
Math.pow(x, 2): 334.541166ms

Note that I am aware that the benchmark is not perfect, and that the values should certainly be taken with a pinch of salt - the aim of the benchmark was to get a ballpark figure.

The Question

While the benchmark gave interesting results it did not model my data accurately, as my data consists largely of 0's. Thus a more accurate test was to run the benchmark without the for loop marked optional. According to the javadoc for Math.pow()

If the first argument is positive zero and the second argument is greater than zero, or the first argument is positive infinity and the second argument is less than zero, then the result is positive zero.

So it would be expected that this benchmark would run faster right!? In reality however, this is considerably slower again:

x*x: 4.3490535ms
Math.pow(x, 2): 3082.1720006ms

Sure, one may expect the math.pow() code to run a little slower than the simple x*x code due to the fact that it needs to work for the general case, but 700x slower? What is going on!? And why is the 0 case so much slower than the Math.random() case?

UPDATE: Updated code and times based on @Stephen C's suggestion. This however made little difference.

Code used to benchmark

Note that reordering the two tests makes negligible difference.

public class Test {
    public Test(){
        int iterations = 100;
        double[] exampleData = new double[5000000];
        double[] test1Results = new double[iterations];
        double[] test2Results = new double[iterations];

        //Optional
        for (int i = 0; i < exampleData.length; i++) {
            exampleData[i] = Math.random();
        }

        for (int i = 0; i < iterations; i++) {
            test1Results[i] = test1(exampleData);
            test2Results[i] = test2(exampleData);
        }
        System.out.println("x*x: " + calculateAverage(test1Results) / 1000000 + "ms");
        System.out.println("Math.pow(x, 2): " + calculateAverage(test2Results) / 1000000 + "ms");
    }

    private long test1(double[] exampleData){
        double total = 0;
        long startTime;
        long endTime;
        startTime = System.nanoTime();
        for (int j = 0; j < exampleData.length; j++) {
            total += exampleData[j] * exampleData[j];
        }
        endTime = System.nanoTime();
        System.out.println(total);
        return endTime - startTime;
    }

    private long test2(double[] exampleData){
        double total = 0;
        long startTime;
        long endTime;
        startTime = System.nanoTime();
        for (int j = 0; j < exampleData.length; j++) {
            total += Math.pow(exampleData[j], 2);
        }
        endTime = System.nanoTime();
        System.out.println(total);
        return endTime - startTime;
    }

    private double calculateAverage(double[] array){
        double total = 0;
        for (int i = 0; i < array.length; i++) {
            total += array[i];
        }
        return total/array.length;
    }

    public static void main(String[] args){
        new Test();
    }
}
like image 692
Hungry Avatar asked Dec 09 '15 21:12

Hungry


People also ask

Why is math POW so slow?

Math. pow is slow because it deals with an equation in the generic sense, using fractional powers to raise it to the given power. It's the lookup it has to go through when computing that takes more time. Simply multiplying numbers together is often faster, since native calls in Java are much more efficient.

What is the time complexity of math POW?

You can consider Math. pow to be O(1).

What is the return type of math pow () function?

The Math. pow() function returns the base to the exponent power, as in base exponent , the base and the exponent are in decimal numeral system.

Does math POW return a double or int?

The method calculates multiplication of the base with itself exponent times and returns the result of type double .


1 Answers

Though this is a bad benchmark, it luckily reveals an interesting effect.

The numbers indicate that you apparently run the benchmark under a "Client" VM. It has not very strong JIT-compiler (known as C1 compiler) that lacks many optimizations. No wonder that it does not work as good as one could expect.

  • Client VM is not smart enough to eliminate Math.pow call even if it has no side effects.
  • Moreover, it does not have a specialized fast path neither for Y=2 nor for X=0. At least, it did not have until Java 9. This has been recently fixed in JDK-8063086 and then further optimized in JDK-8132207.

But an interesting thing is that Math.pow is indeed slower for X=0 with C1 compiler!

But why? Because of implementation details.

x86 architecture does not provide a hardware instruction to compute X^Y. But there are other useful instructions:

  • FYL2X computes Y * log₂X
  • F2XM1 computes 2^X - 1

Thereby, X^Y = 2^(Y * log₂X). Since log₂X is defined only for X > 0, FYL2X ends up with an exception for X=0 and returns -Inf. Thus it happens that X=0 is handled in a slow exceptional path rather than in a specialized fast path.

So what to do?

First of all, stop using Client VM, especially if you care about performance. Switch to the latest JDK 8 in 64-bit flavour, and you'll get the best of C2 optimizing JIT-compiler. And of course, it nicely handles Math.pow(x, 2) etc. Then write a correct benchmark using a proper tool like JMH.

like image 63
apangin Avatar answered Sep 27 '22 22:09

apangin