Improve performance of dependency analyzer

This commit is contained in:
Alexey Andreev 2017-11-10 23:45:12 +03:00
parent e1c7acd8a1
commit d811e7edbb
8 changed files with 255 additions and 89 deletions

View File

@ -15,6 +15,9 @@
*/
package org.teavm.dependency;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.IntSet;
import com.carrotsearch.hppc.cursors.IntCursor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@ -57,6 +60,7 @@ import org.teavm.model.util.ProgramUtils;
import org.teavm.parsing.Parser;
public class DependencyChecker implements DependencyInfo {
private static final int PROPAGATION_STACK_THRESHOLD = 50;
static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true");
private int classNameSuffix;
private DependencyClassSource classSource;
@ -68,6 +72,7 @@ public class DependencyChecker implements DependencyInfo {
private CachedMapper<String, ClassDependency> classCache;
private List<DependencyListener> listeners = new ArrayList<>();
private ServiceRepository services;
private Deque<DependencyNodeToNodeTransition> pendingTransitions = new ArrayDeque<>();
private Deque<Runnable> tasks = new ArrayDeque<>();
private Queue<Runnable> deferredTasks = new ArrayDeque<>();
List<DependencyType> types = new ArrayList<>();
@ -79,6 +84,7 @@ public class DependencyChecker implements DependencyInfo {
private DependencyAgent agent;
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
private boolean completing;
private Map<String, SuperClassFilter> superClassFilters = new HashMap<>();
public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services,
Diagnostics diagnostics) {
@ -237,7 +243,7 @@ public class DependencyChecker implements DependencyInfo {
private int propagationDepth;
void schedulePropagation(DependencyConsumer consumer, DependencyType type) {
if (propagationDepth < 50) {
if (propagationDepth < PROPAGATION_STACK_THRESHOLD) {
++propagationDepth;
consumer.consume(type);
--propagationDepth;
@ -246,6 +252,20 @@ public class DependencyChecker implements DependencyInfo {
}
}
void schedulePropagation(DependencyNodeToNodeTransition consumer, DependencyType type) {
if (consumer.pendingTypes == null && propagationDepth < PROPAGATION_STACK_THRESHOLD) {
++propagationDepth;
consumer.consume(type);
--propagationDepth;
} else {
if (consumer.pendingTypes == null) {
pendingTransitions.add(consumer);
consumer.pendingTypes = new IntOpenHashSet();
}
consumer.pendingTypes.add(type.index);
}
}
void schedulePropagation(DependencyNodeToNodeTransition consumer, DependencyType[] types) {
if (types.length == 0) {
return;
@ -255,14 +275,18 @@ public class DependencyChecker implements DependencyInfo {
return;
}
if (propagationDepth < 50) {
if (consumer.pendingTypes == null && propagationDepth < PROPAGATION_STACK_THRESHOLD) {
++propagationDepth;
consumer.consume(types);
--propagationDepth;
} else {
tasks.add(() -> {
consumer.consume(types);
});
if (consumer.pendingTypes == null) {
pendingTransitions.add(consumer);
consumer.pendingTypes = new IntOpenHashSet();
}
for (DependencyType type : types) {
consumer.pendingTypes.add(type.index);
}
}
}
@ -275,7 +299,7 @@ public class DependencyChecker implements DependencyInfo {
return;
}
if (propagationDepth < 50) {
if (propagationDepth < PROPAGATION_STACK_THRESHOLD) {
++propagationDepth;
for (DependencyType type : types) {
consumer.consume(type);
@ -486,7 +510,7 @@ public class DependencyChecker implements DependencyInfo {
}
FieldDependency dep = new FieldDependency(node, field, fieldRef);
if (!dep.isMissing()) {
tasks.add(() -> linkClass(fieldRef.getClassName(), null).initClass(null));
deferredTasks.add(() -> linkClass(fieldRef.getClassName(), null).initClass(null));
}
return dep;
}
@ -538,9 +562,13 @@ public class DependencyChecker implements DependencyInfo {
return;
}
int index = 0;
while (true) {
while (!tasks.isEmpty()) {
tasks.removeLast().run();
while (!deferredTasks.isEmpty() || !tasks.isEmpty() || !pendingTransitions.isEmpty()) {
while (true) {
processNodeToNodeTransitionQueue();
if (tasks.isEmpty()) {
break;
}
tasks.remove().run();
if (++index == 100) {
if (interruptor != null && !interruptor.shouldContinue()) {
interrupted = true;
@ -549,10 +577,31 @@ public class DependencyChecker implements DependencyInfo {
index = 0;
}
}
if (deferredTasks.isEmpty()) {
break;
propagationDepth = PROPAGATION_STACK_THRESHOLD;
while (!deferredTasks.isEmpty()) {
deferredTasks.remove().run();
}
propagationDepth = 0;
}
}
private void processNodeToNodeTransitionQueue() {
while (!pendingTransitions.isEmpty()) {
DependencyNodeToNodeTransition transition = pendingTransitions.remove();
IntSet pendingTypes = transition.pendingTypes;
transition.pendingTypes = null;
if (pendingTypes.size() == 1) {
DependencyType type = types.get(pendingTypes.iterator().next().value);
transition.consume(type);
} else {
DependencyType[] typesToPropagate = new DependencyType[pendingTypes.size()];
int index = 0;
for (IntCursor cursor : pendingTypes) {
typesToPropagate[index++] = types.get(cursor.value);
}
transition.consume(typesToPropagate);
}
deferredTasks.poll().run();
}
}
@ -611,4 +660,8 @@ public class DependencyChecker implements DependencyInfo {
public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) {
bootstrapMethodSubstitutors.put(method, substitutor);
}
SuperClassFilter getSuperClassFilter(String superClass) {
return superClassFilters.computeIfAbsent(superClass, s -> new SuperClassFilter(classSource, s));
}
}

View File

@ -37,7 +37,7 @@ class DependencyClassSource implements ClassHolderSource {
private List<ClassHolderTransformer> transformers = new ArrayList<>();
private Map<String, ClassHolder> cache = new LinkedHashMap<>();
public DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics) {
DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics) {
this.innerSource = innerSource;
this.diagnostics = diagnostics;
}

View File

@ -17,6 +17,7 @@ package org.teavm.dependency;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -65,7 +66,7 @@ class DependencyGraphBuilder {
private TextLocation currentLocation;
private ExceptionConsumer currentExceptionConsumer;
public DependencyGraphBuilder(DependencyChecker dependencyChecker) {
DependencyGraphBuilder(DependencyChecker dependencyChecker) {
this.dependencyChecker = dependencyChecker;
}
@ -303,9 +304,8 @@ class DependencyGraphBuilder {
}
}
private static class VirtualCallConsumer implements DependencyConsumer {
static class VirtualCallConsumer implements DependencyConsumer {
private final DependencyNode node;
private final String filterClass;
private final MethodDescriptor methodDesc;
private final DependencyChecker checker;
private final DependencyNode[] parameters;
@ -313,14 +313,16 @@ class DependencyGraphBuilder {
private final DefaultCallGraphNode caller;
private final TextLocation location;
private final Set<MethodReference> knownMethods = new HashSet<>();
private final BitSet knownTypes = new BitSet();
private ExceptionConsumer exceptionConsumer;
private SuperClassFilter filter;
public VirtualCallConsumer(DependencyNode node, String filterClass,
VirtualCallConsumer(DependencyNode node, String filterClass,
MethodDescriptor methodDesc, DependencyChecker checker, DependencyNode[] parameters,
DependencyNode result, DefaultCallGraphNode caller, TextLocation location,
ExceptionConsumer exceptionConsumer) {
this.node = node;
this.filterClass = filterClass;
this.filter = checker.getSuperClassFilter(filterClass);
this.methodDesc = methodDesc;
this.checker = checker;
this.parameters = parameters;
@ -332,6 +334,11 @@ class DependencyGraphBuilder {
@Override
public void consume(DependencyType type) {
if (knownTypes.get(type.index)) {
return;
}
knownTypes.set(type.index);
String className = type.getName();
if (DependencyChecker.shouldLog) {
System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". "
@ -339,10 +346,10 @@ class DependencyGraphBuilder {
}
if (className.startsWith("[")) {
className = "java.lang.Object";
type = checker.getType(className);
}
ClassReaderSource classSource = checker.getClassSource();
if (!classSource.isSuperType(filterClass, className).orElse(false)) {
if (!filter.match(type)) {
return;
}
MethodReference methodRef = new MethodReference(className, methodDesc);
@ -351,8 +358,8 @@ class DependencyGraphBuilder {
methodDep.use();
DependencyNode[] targetParams = methodDep.getVariables();
if (parameters[0] != null && targetParams[0] != null) {
parameters[0].connect(targetParams[0], thisType -> classSource.isSuperType(
methodDep.getMethod().getOwnerName(), thisType.getName()).orElse(false));
parameters[0].connect(targetParams[0],
checker.getSuperClassFilter(methodDep.getMethod().getOwnerName()));
}
for (int i = 1; i < parameters.length; ++i) {
if (parameters[i] != null && targetParams[i] != null) {
@ -429,14 +436,9 @@ class DependencyGraphBuilder {
if (targetType instanceof ValueType.Object) {
String targetClsName = ((ValueType.Object) targetType).getClassName();
final ClassReader targetClass = classSource.get(targetClsName);
if (targetClass != null) {
if (targetClass != null && !(targetClass.getName().equals("java.lang.Object"))) {
if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode, type -> {
if (targetClass.getName().equals("java.lang.Object")) {
return true;
}
return classSource.isSuperType(targetClass.getName(), type.getName()).orElse(false);
});
valueNode.connect(receiverNode, dependencyChecker.getSuperClassFilter(targetClass.getName()));
}
return;
}

View File

@ -19,6 +19,7 @@ import java.util.*;
import org.teavm.model.MethodReference;
public class DependencyNode implements ValueDependencyInfo {
private static final int SMALL_TYPES_THRESHOLD = 6;
private DependencyChecker dependencyChecker;
private List<DependencyConsumer> followers;
private int[] smallTypes;
@ -57,8 +58,8 @@ public class DependencyNode implements ValueDependencyInfo {
return false;
}
}
if (smallTypes.length == 5) {
types = new BitSet();
if (smallTypes.length == SMALL_TYPES_THRESHOLD) {
types = new BitSet(dependencyChecker.types.size() * 2);
for (int existingType : smallTypes) {
types.set(existingType);
}
@ -84,9 +85,6 @@ public class DependencyNode implements ValueDependencyInfo {
}
public void propagate(DependencyType type) {
if (type.getDependencyChecker() != dependencyChecker) {
throw new IllegalArgumentException("The given type does not belong to the same dependency checker");
}
if (degree > 2) {
return;
}
@ -94,29 +92,34 @@ public class DependencyNode implements ValueDependencyInfo {
if (DependencyChecker.shouldLog) {
System.out.println(tag + " -> " + type.getName());
}
if (followers != null) {
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
dependencyChecker.schedulePropagation(consumer, type);
}
scheduleSingleType(type);
}
}
private void scheduleSingleType(DependencyType type) {
if (followers != null) {
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
dependencyChecker.schedulePropagation(consumer, type);
}
if (transitions != null) {
for (DependencyConsumer consumer : transitions.toArray(new DependencyConsumer[transitions.size()])) {
dependencyChecker.schedulePropagation(consumer, type);
}
}
if (transitions != null) {
for (DependencyNodeToNodeTransition consumer : transitions.toArray(
new DependencyNodeToNodeTransition[transitions.size()])) {
dependencyChecker.schedulePropagation(consumer, type);
}
}
}
public void propagate(DependencyType[] newTypes) {
DependencyType[] types = new DependencyType[newTypes.length];
if (degree > 2) {
return;
}
int j = 0;
for (int i = 0; i < newTypes.length; ++i) {
DependencyType type = newTypes[i];
if (type.getDependencyChecker() != dependencyChecker) {
throw new IllegalArgumentException("The given type does not belong to the same dependency checker");
}
if (addType(type)) {
types[j++] = type;
newTypes[j++] = type;
}
}
if (j == 0) {
@ -124,22 +127,29 @@ public class DependencyNode implements ValueDependencyInfo {
}
if (DependencyChecker.shouldLog) {
for (int i = 0; i < j; ++i) {
System.out.println(tag + " -> " + types[i].getName());
System.out.println(tag + " -> " + newTypes[i].getName());
}
}
if (j < types.length && (followers != null || transitions != null)) {
types = Arrays.copyOf(types, j);
if (followers == null && transitions == null) {
return;
}
if (j < newTypes.length) {
if (j == 1) {
scheduleSingleType(newTypes[0]);
return;
}
newTypes = Arrays.copyOf(newTypes, j);
}
if (followers != null) {
for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) {
dependencyChecker.schedulePropagation(consumer, types);
dependencyChecker.schedulePropagation(consumer, newTypes);
}
}
if (transitions != null) {
for (DependencyNodeToNodeTransition consumer : transitions.toArray(
new DependencyNodeToNodeTransition[transitions.size()])) {
dependencyChecker.schedulePropagation(consumer, types);
dependencyChecker.schedulePropagation(consumer, newTypes);
}
}
}
@ -250,7 +260,7 @@ public class DependencyNode implements ValueDependencyInfo {
}
return false;
}
return types != null && type.getDependencyChecker() == dependencyChecker && types.get(type.index);
return types != null && types.get(type.index);
}
@Override

View File

@ -15,15 +15,18 @@
*/
package org.teavm.dependency;
import com.carrotsearch.hppc.IntSet;
import java.util.Arrays;
import java.util.BitSet;
class DependencyNodeToNodeTransition implements DependencyConsumer {
private DependencyNode source;
DependencyNode destination;
private DependencyTypeFilter filter;
private BitSet knownFilteredOffTypes;
IntSet pendingTypes;
public DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination,
DependencyTypeFilter filter) {
DependencyNodeToNodeTransition(DependencyNode source, DependencyNode destination, DependencyTypeFilter filter) {
this.source = source;
this.destination = destination;
this.filter = filter;
@ -31,7 +34,7 @@ class DependencyNodeToNodeTransition implements DependencyConsumer {
@Override
public void consume(DependencyType type) {
if (filter != null && !filter.match(type)) {
if (!filterType(type)) {
return;
}
if (type.getName().startsWith("[")) {
@ -47,18 +50,20 @@ class DependencyNodeToNodeTransition implements DependencyConsumer {
}
void consume(DependencyType[] types) {
DependencyType[] filtered = new DependencyType[types.length];
int j = 0;
for (DependencyType type : types) {
if (type.getName().startsWith("[")) {
source.getArrayItem().connect(destination.getArrayItem());
destination.getArrayItem().connect(source.getArrayItem());
}
if (type.getName().equals("java.lang.Class")) {
source.getClassValueNode().connect(destination.getClassValueNode());
}
if ((filter == null || filter.match(type)) && !destination.hasType(type)) {
filtered[j++] = type;
if (filterType(type)) {
if (!destination.hasType(type)) {
types[j++] = type;
}
if (type.getName().startsWith("[")) {
source.getArrayItem().connect(destination.getArrayItem());
destination.getArrayItem().connect(source.getArrayItem());
}
if (type.getName().equals("java.lang.Class")) {
source.getClassValueNode().connect(destination.getClassValueNode());
}
}
}
@ -67,13 +72,32 @@ class DependencyNodeToNodeTransition implements DependencyConsumer {
}
if (j == 1) {
destination.propagate(filtered[0]);
destination.propagate(types[0]);
} else {
if (j < filtered.length) {
filtered = Arrays.copyOf(filtered, j);
if (j < types.length) {
types = Arrays.copyOf(types, j);
}
destination.propagate(filtered);
destination.propagate(types);
}
}
private boolean filterType(DependencyType type) {
if (filter == null) {
return true;
}
if (knownFilteredOffTypes != null && knownFilteredOffTypes.get(type.index)) {
return false;
}
if (!filter.match(type)) {
if (knownFilteredOffTypes == null) {
knownFilteredOffTypes = new BitSet(64);
}
knownFilteredOffTypes.set(type.index);
return false;
}
return true;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.dependency;
import com.carrotsearch.hppc.IntIntMap;
import com.carrotsearch.hppc.IntIntOpenHashMap;
import org.teavm.model.ClassReaderSource;
class SuperClassFilter implements DependencyTypeFilter {
private ClassReaderSource classSource;
private String superType;
private IntIntMap cache = new IntIntOpenHashMap();
SuperClassFilter(ClassReaderSource classSource, String superType) {
this.classSource = classSource;
this.superType = superType;
}
@Override
public boolean match(DependencyType type) {
int result = cache.getOrDefault(type.index, -1);
if (result < 0) {
result = classSource.isSuperType(superType, type.getName()).orElse(false) ? 1 : 0;
cache.put(type.index, result);
}
return result != 0;
}
}

View File

@ -113,24 +113,7 @@ public interface ClassReaderSource {
}
default Optional<Boolean> isSuperType(String superType, String subType) {
if (superType.equals(subType)) {
return Optional.of(true);
}
ClassReader cls = get(subType);
if (cls == null) {
return Optional.empty();
}
if (cls.getParent() != null) {
if (isSuperType(superType, cls.getParent()).orElse(false)) {
return Optional.of(true);
}
}
for (String iface : cls.getInterfaces()) {
if (isSuperType(superType, iface).orElse(false)) {
return Optional.of(true);
}
}
return Optional.of(false);
return ClassReaderSourceHelper.isSuperType(this, superType, subType);
}
default Optional<Boolean> isSuperType(ValueType superType, ValueType subType) {

View File

@ -17,6 +17,7 @@ package org.teavm.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
final class ClassReaderSourceHelper {
@ -59,4 +60,56 @@ final class ClassReaderSourceHelper {
return mostSpecificMethod;
}
static Optional<Boolean> isSuperType(ClassReaderSource classSource, String superType, String subType) {
if (superType.equals("java.lang.Object")) {
return Optional.of(true);
}
ClassReader cls = classSource.get(superType);
if (cls != null && !cls.hasModifier(ElementModifier.INTERFACE)) {
return isSuperTypeSimple(classSource, superType, subType);
}
return isSuperTypeInterface(classSource, superType, subType);
}
private static Optional<Boolean> isSuperTypeSimple(ClassReaderSource classSource,
String superType, String subType) {
while (!superType.equals(subType)) {
ClassReader cls = classSource.get(subType);
if (cls == null) {
return Optional.empty();
}
subType = cls.getParent();
if (subType == null) {
return Optional.of(false);
}
}
return Optional.of(true);
}
private static Optional<Boolean> isSuperTypeInterface(ClassReaderSource classSource,
String superType, String subType) {
if (superType.equals(subType)) {
return Optional.of(true);
}
ClassReader cls = classSource.get(subType);
if (cls == null) {
return Optional.empty();
}
if (cls.getParent() != null) {
if (isSuperTypeInterface(classSource, superType, cls.getParent()).orElse(false)) {
return Optional.of(true);
}
}
for (String iface : cls.getInterfaces()) {
if (isSuperTypeInterface(classSource, superType, iface).orElse(false)) {
return Optional.of(true);
}
}
return Optional.of(false);
}
}