JDK-8077247 : Exponential performance regression Java 8 compiler compared to Java 7 compiler
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8u40
  • Priority: P3
  • Status: Resolved
  • Resolution: Duplicate
  • OS: linux
  • CPU: x86_64
  • Submitted: 2015-02-24
  • Updated: 2015-11-02
  • Resolved: 2015-11-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9Resolved
Related Reports
Blocks :  
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
$ ~/apps/jdk1.7.0_51/bin/java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)

$ ~/apps/jdk1.8.0_31/bin/java -version
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

$ ~/apps/jdk1.8.0_40/bin/java -version
java version "1.8.0_40-ea"
Java(TM) SE Runtime Environment (build 1.8.0_40-ea-b23)
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Linux se-185 3.2.0-4-amd64 #1 SMP Debian 3.2.65-1+deb7u1 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
See Test.java as an example that shows the problem. It is stripped down from the original code, to showcase the problem.

The JDK 7 compiler compiles this very fast. JDK 8 compilers seem to perform exponentially more work, and compilation time blows up. This to me is a severe performance regression, making it practically impossible to perform runtime in-memory code compilation.

For 9 'add' calls, as in Test.java, this is the performance:

$ time ~/apps/jdk1.7.0_51/bin/javac Test.java
real    0m0.418s
user    0m0.632s
sys     0m0.024s
(official Oracle JDK 7u51)

$ time ~/apps/jdk1.8.0_31/bin/javac Test.java
real    0m39.573s
user    0m43.199s
sys     0m0.204s
(official Oracle JDK 8u31)

$ time ~/apps/jdk1.8.0_40/bin/javac Test.java
real    0m37.020s
user    0m41.343s
sys     0m0.204s
(JDK 8u40 Early Access Releases, 8u40 Build b23)

Changing this to 10 'add' calls, as in Test2.java, this is the performance:

$ time ~/apps/jdk1.7.0_51/bin/javac Test2.java
real    0m0.427s
user    0m0.628s
sys     0m0.024s

$ time ~/apps/jdk1.8.0_31/bin/javac Test2.java
real    3m43.917s
user    3m49.058s
sys     0m0.440s

$ time ~/apps/jdk1.8.0_40/bin/javac Test2.java
real    3m34.365s
user    3m38.582s
sys     0m0.456s

It looks like the Java 8 compiler tries for each 'add' call all overloads of the 'add' method, resulting in 5^9=1,953,125 or 5^10=9,765,625 variants to try. This is an exponential blowup.

It should be noted that the Eclipse Luna JDT Java 8 compiler has no issues with this code, and compiles it very fast.

I looked, but could not find an existing bug report for this performance degradation.


REGRESSION.  Last worked in version 7u65

ADDITIONAL REGRESSION INFORMATION: 
See version information in earlier 'Development Kit or Runtime version' field.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
[Test.java]

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        int x = add(add(add(add(add(add(add(add(add(1, 2), 3), 4), 5), 6), 7), 8), 9), 10);
        System.out.println(x);
    }

    public static int add(int x, int y) {
        long rslt = (long)x + (long)y;
        if (Integer.MIN_VALUE <= rslt && rslt <= Integer.MAX_VALUE) {
            return (int)rslt;
        }

        String msg = String.format("Integer overflow: %d + %d.", x, y);
        throw new IllegalArgumentException(msg);
    }

    public static double add(double x, double y) {
        double rslt = x + y;
        if (Double.isInfinite(rslt)) {
            String msg = String.format("Real overflow: %s + %s.", x, y);
            throw new IllegalArgumentException(msg);
        }
        return (rslt == -0.0) ? 0.0 : rslt;
    }

    public static <T> List<T> add(List<T> x, List<T> y) {
        List<T> rslt = new ArrayList<>(x.size() + y.size());
        rslt.addAll(x);
        rslt.addAll(y);
        return rslt;
    }

    public static String add(String x, String y) {
        return x + y;
    }

    public static <K, V> Map<K, V> add(Map<K, V> x, Map<K, V> y) {
        Map<K, V> rslt = new HashMap<>(x);
        rslt.putAll(y);
        return rslt;
    }
}

[Test2.java]

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test2 {
    public static void main(String[] args) {
        int x = add(add(add(add(add(add(add(add(add(add(1, 2), 3), 4), 5), 6), 7), 8), 9), 10), 11);
        System.out.println(x);
    }

    public static int add(int x, int y) {
        long rslt = (long)x + (long)y;
        if (Integer.MIN_VALUE <= rslt && rslt <= Integer.MAX_VALUE) {
            return (int)rslt;
        }

        String msg = String.format("Integer overflow: %d + %d.", x, y);
        throw new IllegalArgumentException(msg);
    }

    public static double add(double x, double y) {
        double rslt = x + y;
        if (Double.isInfinite(rslt)) {
            String msg = String.format("Real overflow: %s + %s.", x, y);
            throw new IllegalArgumentException(msg);
        }
        return (rslt == -0.0) ? 0.0 : rslt;
    }

    public static <T> List<T> add(List<T> x, List<T> y) {
        List<T> rslt = new ArrayList<>(x.size() + y.size());
        rslt.addAll(x);
        rslt.addAll(y);
        return rslt;
    }

    public static String add(String x, String y) {
        return x + y;
    }

    public static <K, V> Map<K, V> add(Map<K, V> x, Map<K, V> y) {
        Map<K, V> rslt = new HashMap<>(x);
        rslt.putAll(y);
        return rslt;
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Renaming the 'add' methods to 'add_int', 'add_double', 'add_string', etc. Makes code generation a bit more involved, though.


Comments
A test for this has been added as part of the changeset for JDK-8078093.
15-09-2015

So the solution for this regression is being implemented as part of the tiered attribution (TA) project: http://openjdk.java.net/jeps/215
08-04-2015

TA javac: Test.java real 0m1.157s user 0m2.856s sys 0m0.155s Test2.java real 0m1.227s user 0m2.926s sys 0m0.156s Amazing! we knew that the TA approach (http://hg.openjdk.java.net/tiered-attrib/dev/langtools) was faster than javac8 but faster than javac7!!! that's another dimension :)
08-04-2015

1) Run the attached test cases (Test.java and Test2.java) 2) Tested this on Linux-X64 and could reproduce the issue. There is a performance degradation from JDK 7 to 8, and 9 (7u80, 8u40, 9 ea b57) **************************************************************************************** Test.java (7u80): (8u40) (9 ea b57) real 3m1.81s real 5m30.87s real 4m8.84s user 0m1.26s user 1m44.39s user 1m36.28s sys 0m0.07s sys 0m0.07s sys 0m0.48s Test2.java (7u80): (8u40) (9 ea b57) real 2m58.51s real 10m12.54s real 9m9.51s user 0m1.24s user 9m31.20s user 8m54.83s sys 0m0.07s sys 0m0.97s sys 0m0.77s ******************************************************************************************
08-04-2015