Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8077247

Exponential performance regression Java 8 compiler compared to Java 7 compiler

    XMLWordPrintable

Details

    • Bug
    • Resolution: Duplicate
    • P3
    • 9
    • 8u40
    • tools
    • x86_64
    • linux

    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.

      Attachments

        Issue Links

          Activity

            People

              vromero Vicente Arturo Romero Zaldivar
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: