diff options
author | Sebastian Lohmeier | 2019-03-30 19:38:03 +0000 |
---|---|---|
committer | Stephan Herrmann | 2019-08-15 19:50:27 +0000 |
commit | 372c6048cc1b21d8a4ceb948bf5eaecaa40576c9 (patch) | |
tree | 49c5753660e77ef7af21dfce8d4bf9b641b84612 | |
parent | 80c9526e9d140ebd12dc7fda8ba293405ffaacf6 (diff) | |
download | eclipse.jdt.core-372c6048cc1b21d8a4ceb948bf5eaecaa40576c9.tar.gz eclipse.jdt.core-372c6048cc1b21d8a4ceb948bf5eaecaa40576c9.tar.xz eclipse.jdt.core-372c6048cc1b21d8a4ceb948bf5eaecaa40576c9.zip |
Bug 543480 - [compiler][inference] Very slowI20190816-1800I20190816-0155
Instead of incorporating the type arguments of parameterized
dependencies one by one, they are incorporated at once - but only if
there are proper types for all inference variables in all type arguments
of a parameterized dependency.
Change-Id: Ic36fecb7b562a31d049b92381fa535400bfb129f
Signed-off-by: Sebastian Lohmeier <sebastian@monochromata.de>
3 files changed, 306 insertions, 19 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java index 20da9ff124..a1248f79fe 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericTypeTest.java @@ -52586,4 +52586,175 @@ public void testBug541772_typeannotations() { compilerOptions, null /*customRequestor*/); } +/** + * This test targets the optimization for parameterized dependencies in {@code BoundSet.combineSameSameWithProperType(...)}. + */ +public void testBug543480BasedOnTest2FromComment4ToSameSameOptimization() { + if (this.complianceLevel >= ClassFileConstants.JDK1_8) { + final long durationFor2TypeParameters = compileTimesAfterWarmup(() -> runConformTest( + new String[] { + "Test2_2.java", + "public class Test2_2 {\n" + + " void test() {\n" + + " m(s(\n" + + " f(1),\n" + + " f(2)\n" + + " ));\n" + + " }\n" + + "\n" + + " static <R> R m(S<R> s) { return null; }\n" + + " static <T> F<T> f(T t) { return null; }\n" + + " static <T1> S<R1<T1>> s(F<T1> t1) { return null; }\n" + + " static <T1, T2> S<R2<T1, T2>> s(F<T1> t1, F<T2> t2) { return null; }\n" + + "}\n" + + "interface F<T> {}\n" + + "interface S<R> {}\n" + + "interface R1<T1> {}\n" + + "interface R2<T1, T2> {}\n" + })); + final long durationFor11TypeParameters = compileTimesAfterWarmup(() -> runConformTest( + new String[] { + "Test2_11.java", + "public class Test2_11 {\n" + + " void test() {\n" + + " m(s(\n" + + " f(1),\n" + + " f(2),\n" + + " f(3),\n" + + " f(4),\n" + + " f(5),\n" + + " f(6),\n" + + " f(7),\n" + + " f(8),\n" + + " f(9),\n" + + " f(10),\n" + + " f(11)\n" + + " ));\n" + + " }\n" + + "\n" + + " static <R> R m(S<R> s) { return null; }\n" + + " static <T> F<T> f(T t) { return null; }\n" + + " static <T1> S<R1<T1>> s(F<T1> t1) { return null; }\n" + + " static <T1, T2> S<R2<T1, T2>> s(F<T1> t1, F<T2> t2) { return null; }\n" + + " static <T1, T2, T3> S<R3<T1, T2, T3>> s(F<T1> t1, F<T2> t2, F<T3> t3) { return null; }\n" + + " static <T1, T2, T3, T4> S<R4<T1, T2, T3, T4>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4) { return null; }\n" + + " static <T1, T2, T3, T4, T5> S<R5<T1, T2, T3, T4, T5>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5) { return null; }\n" + + " static <T1, T2, T3, T4, T5, T6> S<R6<T1, T2, T3, T4, T5, T6>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5, F<T6> t6) { return null; }\n" + + " static <T1, T2, T3, T4, T5, T6, T7> S<R7<T1, T2, T3, T4, T5, T6, T7>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5, F<T6> t6, F<T7> t7) { return null; }\n" + + " static <T1, T2, T3, T4, T5, T6, T7, T8> S<R8<T1, T2, T3, T4, T5, T6, T7, T8>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5, F<T6> t6, F<T7> t7, F<T8> t8) { return null; }\n" + + " static <T1, T2, T3, T4, T5, T6, T7, T8, T9> S<R9<T1, T2, T3, T4, T5, T6, T7, T8, T9>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5, F<T6> t6, F<T7> t7, F<T8> t8, F<T9> t9) { return null; }\n" + + " static <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> S<R10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5, F<T6> t6, F<T7> t7, F<T8> t8, F<T9> t9, F<T10> t10) { return null; }\n" + + " static <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> S<R11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>> s(F<T1> t1, F<T2> t2, F<T3> t3, F<T4> t4, F<T5> t5, F<T6> t6, F<T7> t7, F<T8> t8, F<T9> t9, F<T10> t10, F<T11> t11) { return null; }\n" + + "}\n" + + "interface F<T> {}\n" + + "interface S<R> {}\n" + + "interface R1<T1> {}\n" + + "interface R2<T1, T2> {}\n" + + "interface R3<T1, T2, T3> {}\n" + + "interface R4<T1, T2, T3, T4> {}\n" + + "interface R5<T1, T2, T3, T4, T5> {}\n" + + "interface R6<T1, T2, T3, T4, T5, T6> {}\n" + + "interface R7<T1, T2, T3, T4, T5, T6, T7> {}\n" + + "interface R8<T1, T2, T3, T4, T5, T6, T7, T8> {}\n" + + "interface R9<T1, T2, T3, T4, T5, T6, T7, T8, T9> {}\n" + + "interface R10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> {}\n" + + "interface R11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> {}\n" + })); + // Time complexity should grow roughly linearly, not O(2^n) + // To make the test robust, it tests for the same order of magnitude only, i.e. factor 10. + assertCompileTimes(durationFor2TypeParameters, 10, durationFor11TypeParameters); + } +} +/** + * This test targets the optimization for parameterized dependencies in {@code BoundSet.combineSameSubSuperWithProperType(...)}. + */ +public void testBug543480WithSameSubSuperOptimization() { + if (this.complianceLevel >= ClassFileConstants.JDK1_8) { + final long durationFor2TypeParameters = compileTimesAfterWarmup(() -> runConformTest( + new String[] { + "WithParameterizedDependencies_2.java", + "abstract class WithParameterizedDependencies_2 {\n" + + " <T1, T2> \n" + + " Type1<T1, T2>\n" + + " s1(Type1<T1, T2> t) {\n" + + // This line causes the optimization in BoundSet.combineSameSubSuperWithProperType(...) to be effective. + " return s2(new Type1<>(t));\n" + + " }\n" + + " abstract <E> E s2(E e);\n" + + "}\n" + + "class Type1<T1, T2> {\n" + + " Type1(final Type1<T1, T2> l) {}" + + "}\n" + })); + final long durationFor12TypeParameters = compileTimesAfterWarmup(() -> runConformTest( + new String[] { + "WithParameterizedDependencies_12.java", + "abstract class WithParameterizedDependencies_12 {\n" + + " <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> \n" + + " Type1<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>\n" + + " s1(Type1<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> t) {\n" + + // This line causes the optimization in BoundSet.combineSameSubSuperWithProperType(...) to be effective. + " return s2(new Type1<>(t));\n" + + " }\n" + + " abstract <E> E s2(E e);\n" + + "}\n" + + "class Type1<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> {\n" + + " Type1(final Type1<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> l) {}" + + "}\n" + })); + // Time complexity should grow roughly linearly, not O(2^n). + // To make the test robust, it tests for the same order of magnitude only, i.e. factor 10. + assertCompileTimes(durationFor2TypeParameters, 10, durationFor12TypeParameters); + } +} +/** + * An earlier version of the fix for bug 543480 causes a NullPointerException when compiling this code. + */ +public void testBug543480WithoutNullPointerExceptionDuringBytecodeGeneration() { + if (this.complianceLevel >= ClassFileConstants.JDK1_8) { + runConformTest( + new String[] { + "Test.java", + "import java.util.function.BiConsumer;\n" + + "\n" + + "public class Test {\n" + + " \n" + + " interface I0<T extends I0<T>> {}\n" + + "\n" + + " class Type<T extends I0<T>, D> {\n" + + " // TODO: The problem might be that BiConsumer is not declared in the same file?\n" + + " public Type(final BiConsumer<T, D> b) { }\n" + + " }\n" + + "\n" + + " public void foo() {\n" + + " new Type<>((unused0, unused1) -> {});\n" + + " }\n" + + "}" + }); + } +} + +protected void assertCompileTimes(final long shortTime, final double factor, final long longTime) { + assertTrue(longTime + " should be less than " + factor + "x the compile time of " + shortTime, + longTime < factor*shortTime); +} + +protected long compileTimesAfterWarmup(final Runnable compileTask) { + // warm up + duration(compileTask); + // average + return (duration(compileTask) + duration(compileTask) + + duration(compileTask) + duration(compileTask) + + duration(compileTask) + duration(compileTask)) / 6; } + +protected long duration(final Runnable runnable) { + final long startMs = System.currentTimeMillis(); + runnable.run(); + final long endMs = System.currentTimeMillis(); + final long duration = endMs-startMs; + return duration; +} + +} + diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java index 9c015d394c..6760a7bddd 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java @@ -660,13 +660,16 @@ public boolean verifyClassFilesThrowingError(String sourceFilePath, String class */ private void waitForFullBuffers() { String endString = VerifyTests.class.getName(); - int count = 50; + int count = 60; + int waitMs = 1; int errorEndStringStart = this.errorBuffer.toString().indexOf(endString); int outputEndStringStart = this.outputBuffer.toString().indexOf(endString); while (errorEndStringStart == -1 || outputEndStringStart == -1) { try { - Thread.sleep(100); + Thread.sleep(waitMs); } catch (InterruptedException e) { + } finally { + if(waitMs < 100) waitMs *= 2; } if (--count == 0) return; errorEndStringStart = this.errorBuffer.toString().indexOf(endString); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java index 78cff89fd4..d004bd72d5 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java @@ -16,14 +16,21 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Stream; import org.eclipse.jdt.internal.compiler.ast.Wildcard; @@ -33,6 +40,18 @@ import org.eclipse.jdt.internal.compiler.ast.Wildcard; */ class BoundSet { + /** + * Set the identically-named system property to false to disable the + * optimization implemented to fix Bug 543480. + */ + public static boolean enableOptimizationForBug543480 = true; + static { + String enableOptimizationForBug543480Property = System.getProperty("enableOptimizationForBug543480"); //$NON-NLS-1$ + if(enableOptimizationForBug543480Property != null) { + enableOptimizationForBug543480 = enableOptimizationForBug543480Property.equalsIgnoreCase("true"); //$NON-NLS-1$ + } + } + static final BoundSet TRUE = new BoundSet(); // empty set of bounds static final BoundSet FALSE = new BoundSet(); // pseudo bounds @@ -536,18 +555,18 @@ class BoundSet { case ReductionResult.SAME: switch (boundJ.relation) { case ReductionResult.SAME: - newConstraint = combineSameSame(boundI, boundJ); + newConstraint = combineSameSame(boundI, boundJ, first, next); break; case ReductionResult.SUBTYPE: case ReductionResult.SUPERTYPE: - newConstraint = combineSameSubSuper(boundI, boundJ); + newConstraint = combineSameSubSuper(boundI, boundJ, first, next); break; } break; case ReductionResult.SUBTYPE: switch (boundJ.relation) { case ReductionResult.SAME: - newConstraint = combineSameSubSuper(boundJ, boundI); + newConstraint = combineSameSubSuper(boundJ, boundI, first, next); break; case ReductionResult.SUPERTYPE: newConstraint = combineSuperAndSub(boundJ, boundI); @@ -561,7 +580,7 @@ class BoundSet { case ReductionResult.SUPERTYPE: switch (boundJ.relation) { case ReductionResult.SAME: - newConstraint = combineSameSubSuper(boundJ, boundI); + newConstraint = combineSameSubSuper(boundJ, boundI, first, next); break; case ReductionResult.SUBTYPE: newConstraint = combineSuperAndSub(boundI, boundJ); @@ -726,7 +745,7 @@ class BoundSet { reduceOneConstraint(context, formula); } - private ConstraintTypeFormula combineSameSame(TypeBound boundS, TypeBound boundT) { + private ConstraintTypeFormula combineSameSame(TypeBound boundS, TypeBound boundT, TypeBound[] firstBounds, TypeBound[] nextBounds) { // α = S and α = T imply ⟨S = T⟩ if (TypeBinding.equalsEquals(boundS.left, boundT.left)) @@ -734,19 +753,26 @@ class BoundSet { // match against more shapes: ConstraintTypeFormula newConstraint; - newConstraint = combineSameSameWithProperType(boundS, boundT); + newConstraint = combineSameSameWithProperType(boundS, boundT, firstBounds, nextBounds); if (newConstraint != null) return newConstraint; - newConstraint = combineSameSameWithProperType(boundT, boundS); + newConstraint = combineSameSameWithProperType(boundT, boundS, firstBounds, nextBounds); if (newConstraint != null) return newConstraint; return null; } // pre: boundLeft.left != boundRight.left - private ConstraintTypeFormula combineSameSameWithProperType(TypeBound boundLeft, TypeBound boundRight) { + private ConstraintTypeFormula combineSameSameWithProperType(TypeBound boundLeft, TypeBound boundRight, + TypeBound[] firstBounds, TypeBound[] nextBounds) { // α = U and S = T imply ⟨S[α:=U] = T[α:=U]⟩ TypeBinding u = boundLeft.right; + if (enableOptimizationForBug543480 && isParameterizedDependency(boundRight)) { + // Performance optimization: do not incorporate arguments one by one, which yielt 2^n new bounds (n=number of type arguments) in the past. + // Instead, all arguments of a parameterized dependency are incorporated at once - but only when they are available. + return incorporateIntoParameterizedDependencyIfAllArgumentsAreProperTypes(boundRight, + firstBounds, nextBounds); + } if (u.isProperType(true)) { InferenceVariable alpha = boundLeft.left; TypeBinding left = boundRight.left; // no substitution since S inference variable and (S != α) per precondition @@ -756,7 +782,7 @@ class BoundSet { return null; } - private ConstraintTypeFormula combineSameSubSuper(TypeBound boundS, TypeBound boundT) { + private ConstraintTypeFormula combineSameSubSuper(TypeBound boundS, TypeBound boundT, TypeBound[] firstBounds, TypeBound[] nextBounds) { // α = S and α <: T imply ⟨S <: T⟩ // α = S and T <: α imply ⟨T <: S⟩ InferenceVariable alpha = boundS.left; @@ -783,20 +809,33 @@ class BoundSet { return ConstraintTypeFormula.create(t, s, boundT.relation, boundT.isSoft||boundS.isSoft); } } + return combineSameSubSuperWithProperType(boundS, boundT, alpha, firstBounds, nextBounds); + } + + // pre: boundLeft.left != boundRight.left + // pre: boundLeft.left != boundRight.right + private ConstraintTypeFormula combineSameSubSuperWithProperType(TypeBound boundLeft, TypeBound boundRight, + InferenceVariable alpha, TypeBound[] firstBounds, TypeBound[] nextBounds) { + // α = U and S <: T imply ⟨S[α:=U] <: T[α:=U]⟩ + TypeBinding u = boundLeft.right; - // α = U and S <: T imply ⟨S[α:=U] <: T[α:=U]⟩ - TypeBinding u = boundS.right; + if (enableOptimizationForBug543480 && isParameterizedDependency(boundRight)) { + // Performance optimization: do not incorporate arguments one by one, which yielt 2^n new bounds (n=number of type arguments) in the past. + // Instead, all arguments of a parameterized dependency are incorporated at once - but only when they are available. + return incorporateIntoParameterizedDependencyIfAllArgumentsAreProperTypes(boundRight, + firstBounds, nextBounds); + } if (u.isProperType(true)) { - boolean substitute = TypeBinding.equalsEquals(alpha, boundT.left); - TypeBinding left = substitute ? u : boundT.left; - TypeBinding right = boundT.right.substituteInferenceVariable(alpha, u); - substitute |= TypeBinding.notEquals(right, boundT.right); + boolean substitute = TypeBinding.equalsEquals(alpha, boundRight.left); + TypeBinding left = substitute ? u : boundRight.left; + TypeBinding right = boundRight.right.substituteInferenceVariable(alpha, u); + substitute |= TypeBinding.notEquals(right, boundRight.right); if (substitute) // avoid redundant constraint - return ConstraintTypeFormula.create(left, right, boundT.relation, boundT.isSoft||boundS.isSoft); + return ConstraintTypeFormula.create(left, right, boundRight.relation, boundRight.isSoft||boundLeft.isSoft); } return null; } - + private ConstraintTypeFormula combineSuperAndSub(TypeBound boundS, TypeBound boundT) { // permutations of: S <: α and α <: T imply ⟨S <: T⟩ InferenceVariable alpha = boundS.left; @@ -824,6 +863,80 @@ class BoundSet { return null; } + private boolean isParameterizedDependency(TypeBound typeBound) { + return typeBound.right.kind() == Binding.PARAMETERIZED_TYPE + && !typeBound.right.isProperType(true) /* is a dependency, not a type bound */ + && typeBound.right.isParameterizedTypeWithActualArguments(); + } + + private ConstraintTypeFormula incorporateIntoParameterizedDependencyIfAllArgumentsAreProperTypes(TypeBound typeBound, + TypeBound[] firstBounds, TypeBound[] nextBounds) { + Collection<TypeBound> properTypesForAllInferenceVariables = getProperTypesForAllInferenceVariablesOrNull((ParameterizedTypeBinding)typeBound.right, firstBounds, nextBounds); + if (null != properTypesForAllInferenceVariables) { + return combineWithProperTypes(properTypesForAllInferenceVariables, typeBound); + } + return null; + } + + private Collection<TypeBound> getProperTypesForAllInferenceVariablesOrNull(ParameterizedTypeBinding parameterizedType, + TypeBound[] firstBounds, TypeBound[] nextBounds) { + final Map<InferenceVariable,TypeBound> properTypesByInferenceVariable = properTypesByInferenceVariable(firstBounds, nextBounds); + if(properTypesByInferenceVariable.size() == 0) { + return null; + } + final Set<InferenceVariable> inferenceVariables = getInferenceVariables(parameterizedType); + if(properTypesByInferenceVariable.keySet().containsAll(inferenceVariables)) { + return properTypesByInferenceVariable.values(); + } + return null; + } + + private Map<InferenceVariable,TypeBound> properTypesByInferenceVariable(TypeBound[] firstBounds, TypeBound[] nextBounds) { + return getBoundsStream(firstBounds, nextBounds) + .filter(bound -> bound.relation == ReductionResult.SAME) + .filter(bound -> bound.right.isProperType(true)) + .collect(toMap(bound -> bound.left, identity(), + // If nextBounds and firstBounds have a bound for the IV, prefer the newer one from nextBounds. + (boundFromNextBounds, boundFromFirstBounds) -> boundFromNextBounds)); + } + + private Stream<TypeBound> getBoundsStream(TypeBound[] firstBounds, TypeBound[] nextBounds) { + if(firstBounds == nextBounds) { + return Arrays.stream(firstBounds); + } + // The next bounds are considered initially because it seems more + // likely that they contain the new bounds that enable successful + // incorporation in this run in case no incorporation was possible + // in previous runs. + return Stream.concat(Arrays.stream(nextBounds), Arrays.stream(firstBounds)); + } + + private Set<InferenceVariable> getInferenceVariables(ParameterizedTypeBinding parameterizedType) { + final Set<InferenceVariable> inferenceVariables = new LinkedHashSet<>(); + for(final TypeBinding argument: parameterizedType.arguments) { + argument.collectInferenceVariables(inferenceVariables); + } + return inferenceVariables; + } + + private ConstraintTypeFormula combineWithProperTypes(Collection<TypeBound> properTypesForAllInferenceVariables, TypeBound boundRight) { + // either: α = U, β = V, ... and S = T imply ⟨S[α:=U, β:=V, ...] = T[α:=U, β:=V, ...]⟩ + // or: α = U, β = V, ... and S <: T imply ⟨S[α:=U, β:=V, ...] <: T[α:=U, β:=V, ...]⟩ + if(properTypesForAllInferenceVariables.size() == 0) { + return null; + } + boolean isAnyLeftSoft = false; + InferenceVariable left = boundRight.left; + TypeBinding right = boundRight.right; + for(final TypeBound properTypeForInferenceVariable: properTypesForAllInferenceVariables) { + final TypeBound boundLeft = properTypeForInferenceVariable; + final InferenceVariable alpha = boundLeft.left; + final TypeBinding u = boundLeft.right; + isAnyLeftSoft |= boundLeft.isSoft; + right = right.substituteInferenceVariable(alpha, u); + } + return ConstraintTypeFormula.create(left, right, boundRight.relation, isAnyLeftSoft||boundRight.isSoft); + } private ConstraintTypeFormula[] deriveTypeArgumentConstraints(TypeBound boundS, TypeBound boundT) { /* From 18.4: |