8189747: JDK9 javax.lang.model.util.Elements#getTypeElement regressed 1000x in performance.
Summary: Caching the results of Elements.getTypeElement/getPackageElement
Reviewed-by: darcy
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java Sun Jul 15 20:15:17 2018 +0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java Mon Jul 16 12:58:49 2018 +0200
@@ -26,9 +26,11 @@
package com.sun.tools.javac.model;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -184,7 +186,9 @@
}
private final Set<String> alreadyWarnedDuplicates = new HashSet<>();
+ private final Map<Pair<String, String>, Optional<Symbol>> resultCache = new HashMap<>();
+ @SuppressWarnings("unchecked")
private <S extends Symbol> S unboundNameToSymbol(String methodName,
String nameStr,
Class<S> clazz) {
@@ -192,44 +196,46 @@
return nameToSymbol(syms.noModule, nameStr, clazz);
}
- Set<S> found = new LinkedHashSet<>();
+ return (S) resultCache.computeIfAbsent(Pair.of(methodName, nameStr), p -> {
+ Set<S> found = new LinkedHashSet<>();
- for (ModuleSymbol msym : modules.allModules()) {
- S sym = nameToSymbol(msym, nameStr, clazz);
+ for (ModuleSymbol msym : modules.allModules()) {
+ S sym = nameToSymbol(msym, nameStr, clazz);
- if (sym == null)
- continue;
+ if (sym == null)
+ continue;
- if (clazz == ClassSymbol.class) {
- // Always include classes
- found.add(sym);
- } else if (clazz == PackageSymbol.class) {
- // In module mode, ignore the "spurious" empty packages that "enclose" module-specific packages.
- // For example, if a module contains classes or package info in package p.q.r, it will also appear
- // to have additional packages p.q and p, even though these packages have no content other
- // than the subpackage. We don't want those empty packages showing up in searches for p or p.q.
- if (!sym.members().isEmpty() || ((PackageSymbol) sym).package_info != null) {
+ if (clazz == ClassSymbol.class) {
+ // Always include classes
found.add(sym);
+ } else if (clazz == PackageSymbol.class) {
+ // In module mode, ignore the "spurious" empty packages that "enclose" module-specific packages.
+ // For example, if a module contains classes or package info in package p.q.r, it will also appear
+ // to have additional packages p.q and p, even though these packages have no content other
+ // than the subpackage. We don't want those empty packages showing up in searches for p or p.q.
+ if (!sym.members().isEmpty() || ((PackageSymbol) sym).package_info != null) {
+ found.add(sym);
+ }
}
}
- }
- if (found.size() == 1) {
- return found.iterator().next();
- } else if (found.size() > 1) {
- //more than one element found, produce a note:
- if (alreadyWarnedDuplicates.add(methodName + ":" + nameStr)) {
- String moduleNames = found.stream()
- .map(s -> s.packge().modle)
- .map(m -> m.toString())
- .collect(Collectors.joining(", "));
- log.note(Notes.MultipleElements(methodName, nameStr, moduleNames));
+ if (found.size() == 1) {
+ return Optional.of(found.iterator().next());
+ } else if (found.size() > 1) {
+ //more than one element found, produce a note:
+ if (alreadyWarnedDuplicates.add(methodName + ":" + nameStr)) {
+ String moduleNames = found.stream()
+ .map(s -> s.packge().modle)
+ .map(m -> m.toString())
+ .collect(Collectors.joining(", "));
+ log.note(Notes.MultipleElements(methodName, nameStr, moduleNames));
+ }
+ return Optional.empty();
+ } else {
+ //not found:
+ return Optional.empty();
}
- return null;
- } else {
- //not found, or more than one element found:
- return null;
- }
+ }).orElse(null);
}
/**
@@ -787,4 +793,8 @@
throw new IllegalArgumentException(o.toString());
return clazz.cast(o);
}
+
+ public void newRound() {
+ resultCache.clear();
+ }
}
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Sun Jul 15 20:15:17 2018 +0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Mon Jul 16 12:58:49 2018 +0200
@@ -1270,6 +1270,7 @@
modules.newRound();
types.newRound();
annotate.newRound();
+ elementUtils.newRound();
boolean foundError = false;
--- a/test/langtools/tools/javac/modules/AnnotationProcessing.java Sun Jul 15 20:15:17 2018 +0300
+++ b/test/langtools/tools/javac/modules/AnnotationProcessing.java Mon Jul 16 12:58:49 2018 +0200
@@ -23,7 +23,7 @@
/**
* @test
- * @bug 8133884 8162711 8133896 8172158 8172262 8173636 8175119
+ * @bug 8133884 8162711 8133896 8172158 8172262 8173636 8175119 8189747
* @summary Verify that annotation processing works.
* @library /tools/lib
* @modules
@@ -1389,6 +1389,19 @@
.run()
.writeAll();
+ //from source:
+ new JavacTask(tb)
+ .options("--module-source-path", moduleSrc.toString(),
+ "--source-path", src.toString(),
+ "-processorpath", System.getProperty("test.class.path"),
+ "-processor", UnboundLookupGenerate.class.getName(),
+ "-XDrawDiagnostics")
+ .outdir(classes)
+ .files(findJavaFiles(moduleSrc))
+ .run()
+ .writeAll()
+ .getOutput(OutputKind.DIRECT);
+
}
@SupportedAnnotationTypes("*")
@@ -1486,6 +1499,29 @@
}
+ @SupportedAnnotationTypes("*")
+ public static final class UnboundLookupGenerate extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (processingEnv.getElementUtils().getTypeElement("nue.Nue") == null) {
+ try (Writer w = processingEnv.getFiler().createSourceFile("m1x/nue.Nue").openWriter()) {
+ w.write("package nue; public class Nue {}");
+ } catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ }
+
@Test
public void testWrongDefaultTargetModule(Path base) throws Exception {
Path src = base.resolve("src");