Improve performance of if rules with fixed class name
This improves the build speed of an app that contains thousands of Moshi generated -if rules by 10-20%.
Bug: b/378464445
Change-Id: I8c84281c6d2ff3d08ccf81962a0541ed87077571
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 583380c..4dcaed1 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
@@ -19,6 +20,7 @@
import com.android.tools.r8.threading.TaskCollection;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.UncheckedExecutionException;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.base.Equivalence.Wrapper;
@@ -122,8 +124,10 @@
public void processActiveIfRulesWithoutMembers(
Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
- Iterable<DexProgramClass> newlyLiveClasses)
+ Map<DexType, DexProgramClass> newlyLiveClasses,
+ Timing timing)
throws ExecutionException {
+ List<DexProgramClass> classMatches = new ArrayList<>(newlyLiveClasses.size());
MapUtils.removeIf(
ifRules,
(ifRuleWrapper, ifRulesInEquivalence) -> {
@@ -131,15 +135,11 @@
// -keep rule may vary (due to back references). So, we need to try all pairs of -if
// rule and live types.
ProguardIfRule ifRuleKey = ifRuleWrapper.get();
- List<DexProgramClass> classMatches = new ArrayList<>();
- for (DexProgramClass clazz : newlyLiveClasses) {
- if (evaluateClassForIfRule(ifRuleKey, clazz)) {
- classMatches.add(clazz);
- }
- }
+ evaluateClassesForIfRule(ifRuleKey, newlyLiveClasses, classMatches, timing);
if (!classMatches.isEmpty()) {
ifRulesInEquivalence.removeIf(
ifRule -> processActiveIfRuleWithWithoutMembers(ifRule, classMatches));
+ classMatches.clear();
}
return ifRulesInEquivalence.isEmpty();
});
@@ -206,6 +206,32 @@
}
}
+ private void evaluateClassesForIfRule(
+ ProguardIfRule rule,
+ Map<DexType, DexProgramClass> newlyLiveClasses,
+ List<DexProgramClass> classMatches,
+ Timing timing) {
+ timing.begin("Evaluate precondition");
+ if (rule.getClassNames().hasSingleSpecificType()) {
+ timing.begin("Evaluate single class");
+ DexType singleSpecificType = rule.getClassNames().getSingleSpecificType();
+ DexProgramClass singleSpecificClass = newlyLiveClasses.get(singleSpecificType);
+ if (singleSpecificClass != null && evaluateClassForIfRule(rule, singleSpecificClass)) {
+ classMatches.add(singleSpecificClass);
+ }
+ timing.end();
+ } else {
+ for (DexProgramClass clazz : newlyLiveClasses.values()) {
+ timing.begin("Evaluate class");
+ if (evaluateClassForIfRule(rule, clazz)) {
+ classMatches.add(clazz);
+ }
+ timing.end();
+ }
+ }
+ timing.end();
+ }
+
/** Determines if {@param clazz} satisfies the given if-rule class specification. */
private boolean evaluateClassForIfRule(ProguardIfRule rule, DexProgramClass clazz) {
incrementNumberOfProguardIfRuleClassEvaluations();
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
index 497f691..2e58f76 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
@@ -30,6 +31,7 @@
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@@ -151,7 +153,7 @@
ConsequentRootSet consequentRootSet =
timing.time(
"Find consequent items for -if rules...",
- () -> processActiveIfRules(enqueuer, isFirstFixpoint));
+ () -> processActiveIfRules(enqueuer, isFirstFixpoint, timing));
enqueuer.addConsequentRootSet(consequentRootSet);
assert enqueuer.getNumberOfLiveItems() == numberOfLiveItemsAtStart;
}
@@ -166,19 +168,23 @@
return !classesWithNewlyLiveMembers.isEmpty();
}
- private ConsequentRootSet processActiveIfRules(Enqueuer enqueuer, boolean isFirstFixpoint)
- throws ExecutionException {
+ private ConsequentRootSet processActiveIfRules(
+ Enqueuer enqueuer, boolean isFirstFixpoint, Timing timing) throws ExecutionException {
SubtypingInfo subtypingInfo = enqueuer.getSubtypingInfo();
ConsequentRootSetBuilder consequentRootSetBuilder =
ConsequentRootSet.builder(appView, enqueuer, subtypingInfo);
IfRuleEvaluator evaluator =
new IfRuleEvaluator(appView, subtypingInfo, enqueuer, consequentRootSetBuilder, tasks);
+ timing.begin("If rules with members");
if (shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)) {
processActiveIfRulesWithMembers(evaluator, isFirstFixpoint);
}
+ timing.end();
+ timing.begin("If rules without members");
if (shouldProcessActiveIfRulesWithoutMembers(isFirstFixpoint)) {
- processActiveIfRulesWithoutMembers(evaluator, isFirstFixpoint);
+ processActiveIfRulesWithoutMembers(evaluator, isFirstFixpoint, timing);
}
+ timing.end();
return consequentRootSetBuilder.buildConsequentRootSet();
}
@@ -211,13 +217,20 @@
}
private void processActiveIfRulesWithoutMembers(
- IfRuleEvaluator evaluator, boolean isFirstFixpoint) throws ExecutionException {
+ IfRuleEvaluator evaluator, boolean isFirstFixpoint, Timing timing) throws ExecutionException {
if (isFirstFixpoint && !effectivelyFakeLiveClasses.isEmpty()) {
+ Map<DexType, DexProgramClass> newlyLiveClassesMap =
+ new IdentityHashMap<>(effectivelyFakeLiveClasses.size() + newlyLiveClasses.size());
+ effectivelyFakeLiveClasses.forEach(clazz -> newlyLiveClassesMap.put(clazz.getType(), clazz));
+ newlyLiveClasses.forEach(clazz -> newlyLiveClassesMap.put(clazz.getType(), clazz));
evaluator.processActiveIfRulesWithoutMembers(
- activeIfRulesWithoutMembers,
- Iterables.concat(effectivelyFakeLiveClasses, newlyLiveClasses));
+ activeIfRulesWithoutMembers, newlyLiveClassesMap, timing);
} else {
- evaluator.processActiveIfRulesWithoutMembers(activeIfRulesWithoutMembers, newlyLiveClasses);
+ Map<DexType, DexProgramClass> newlyLiveClassesMap =
+ new IdentityHashMap<>(newlyLiveClasses.size());
+ newlyLiveClasses.forEach(clazz -> newlyLiveClassesMap.put(clazz.getType(), clazz));
+ evaluator.processActiveIfRulesWithoutMembers(
+ activeIfRulesWithoutMembers, newlyLiveClassesMap, timing);
}
newlyLiveClasses.clear();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 37c0cf3..58d855f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -85,9 +85,17 @@
@Override
public abstract int hashCode();
+ public abstract boolean hasSingleSpecificType();
+
+ public DexType getSingleSpecificType() {
+ return null;
+ }
+
public abstract boolean hasSpecificTypes();
- public abstract Set<DexType> getSpecificTypes();
+ public Set<DexType> getSpecificTypes() {
+ return null;
+ }
public abstract boolean matches(DexType type);
@@ -165,13 +173,13 @@
}
@Override
- public boolean hasSpecificTypes() {
+ public boolean hasSingleSpecificType() {
return false;
}
@Override
- public Set<DexType> getSpecificTypes() {
- return null;
+ public boolean hasSpecificTypes() {
+ return false;
}
@Override
@@ -227,6 +235,16 @@
}
@Override
+ public boolean hasSingleSpecificType() {
+ return className.hasSpecificType();
+ }
+
+ @Override
+ public DexType getSingleSpecificType() {
+ return className.getSpecificType();
+ }
+
+ @Override
public boolean hasSpecificTypes() {
return className.hasSpecificType() || className.hasSpecificTypes();
}
@@ -278,6 +296,7 @@
private PositiveClassNameList(Collection<ProguardTypeMatcher> classNames) {
this.classNames = ImmutableList.copyOf(classNames);
+ assert !hasSpecificTypes() || getSpecificTypes().size() > 1;
}
@Override
@@ -316,6 +335,11 @@
}
@Override
+ public boolean hasSingleSpecificType() {
+ return false;
+ }
+
+ @Override
public boolean hasSpecificTypes() {
return getSpecificTypes() != null;
}
@@ -426,13 +450,13 @@
}
@Override
- public boolean hasSpecificTypes() {
+ public boolean hasSingleSpecificType() {
return false;
}
@Override
- public Set<DexType> getSpecificTypes() {
- return null;
+ public boolean hasSpecificTypes() {
+ return false;
}
@Override