Inliner heuristic: prevent inlining based on code item size.
It's impossible to know the the exact number of `HInstruction`s that
will be generated for an inlined method, but there is some loose
correlation between this number and code item size. Namely, the number
of `HInstruction`s is on average equal to 2/3 of code item size (this
was determined by compiling top 100 apps with an instrumented version
of dex2oat that printed the two numbers).
This heuristic is a bit stricter: it prevents methods from inlining if
3/4 of their code item size exceeds inlining budget. The decision to
use 3/4 instead of 2/3 is based on experimental run on top 100 apps
with two metrics (computed based on inliner stats as reported by
--dump-stats):
A. the number of previously successful inline attempts that got
cancelled due to heuristic, measured as (X - Y)/X, where X, Y is
sum of the following inliner stats without and with heuristic:
- InlinedInvoke
- InlinedPolymorphicCall
- InlinedLastInvokeVirtualOrInterface
B. the number of previously failed inline attempts that got cancelled
due to heuristic (preventing waste of resources and compile time),
measured as (X - Y)/X, where X, Y is sum of the following inliner
stats without and with heuristic:
- NotInlinedInstructionBudget
- NotInlinedAlwaysThrows
- NotInlinedInfiniteLoop
- NotInlinedIrreducibleLoopCaller
- NotInlinedIrreducibleLoopCallee
- NotInlinedLoopWithoutExit
- NotInlinedStackMaps
- NotInlinedUnresolvedEntrypoint
- NotInlinedBss
- NotInlinedCannotBuild
Metrics for factors 1/1 > 4/5 > 3/4 (chosen) > 5/7 > 2/3 > 1/2:
With heuristic factor 1/1:
- metric A is at max 3.3% for any given app and 0.239% on average
- metric B is at max 97.6% and 42.7% on average
With heuristic factor 4/5:
- metric A is at max 3.3% for any given app and 0.131% on average
- metric B is at max 97.6% and 42.2% on average
With heuristic factor 3/4 (the chosen value):
- metric A is at max 0.5% for any given app and 0.054% on average
- metric B is at max 97.3% and 41.3% on average
With heuristic factor 5/7:
- metric A is at max 0.5% for any given app and 0.054% on average
- metric B is at max 97.3% and 41.1% on average
With heuristic factor 2/3:
- metric A is at max 0.5% for any given app and 0.053% on average
- metric B is at max 96.9% and 40.9% on average
With heuristic factor 1/2:
- metric A is at max 0.2% for any given app and 0.007% on average
- metric B is at max 77.7% and 32.8% on average
Factor 3/4 was chosen as it is the highest factor that still has a low
percent of prevented-successful, but high percent of prevented-failed
inline attempts.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Bug: 393108375
Flag: EXEMPT Behavioural changes (no significant impact)
Change-Id: I9adc6c21fc23220046fdba33ae47a7e991acc666
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index f0f0a73..cda8d49 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -1600,6 +1600,21 @@
return false;
}
+ // The heuristic below tries to prevent inline attempts where the graph is built but discarded
+ // later due to its size being over the budget. A rough estimate of method size (`HInstruction`
+ // count) is 2/3 of code item size (it is not always true - a large code item may result in just
+ // a few `HInstruction`s, but experiments show such methods are relatively rare). We use factor
+ // 3/4 rather than 2/3 as the experiments show that it results in approximately the same number
+ // of prevented-successful inline attempts, but higher prevented-failed attempts.
+ size_t estimated_size = (accessor.InsnsSizeInCodeUnits() * 3u) / 4u;
+ if (estimated_size > inlining_budget_) {
+ LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedCodeItem)
+ << "Method " << method->PrettyMethod()
+ << " is not inlined because its estimated size based on code item exceeds inlining budget: "
+ << estimated_size << " > " << inlining_budget_;
+ return false;
+ }
+
return true;
}