1 /*
2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 // #define LOG_NDEBUG 0
18
19 #undef LOG_TAG
20 #define LOG_TAG "Planner"
21
22 #include <compositionengine/impl/planner/Predictor.h>
23
24 namespace android::compositionengine::impl::planner {
25
getApproximateMatch(const std::vector<const LayerState * > & other) const26 std::optional<LayerStack::ApproximateMatch> LayerStack::getApproximateMatch(
27 const std::vector<const LayerState*>& other) const {
28 // Differing numbers of layers are never an approximate match
29 if (mLayers.size() != other.size()) {
30 return std::nullopt;
31 }
32
33 std::optional<ApproximateMatch> approximateMatch = {};
34 for (size_t i = 0; i < mLayers.size(); ++i) {
35 // Skip identical layers
36 if (mLayers[i].getHash() == other[i]->getHash()) {
37 continue;
38 }
39
40 // Skip layers where both are client-composited, since that doesn't change the
41 // composition plan
42 if (mLayers[i].getCompositionType() == hal::Composition::CLIENT &&
43 other[i]->getCompositionType() == hal::Composition::CLIENT) {
44 continue;
45 }
46
47 // If layers differ in composition type, their stacks are too different
48 if (mLayers[i].getCompositionType() != other[i]->getCompositionType()) {
49 return std::nullopt;
50 }
51
52 // If layers are not identical, but we already detected a prior approximate match for a
53 // previous layer, the LayerStacks differ by too much, so return nothing
54 if (approximateMatch) {
55 return std::nullopt;
56 }
57
58 Flags<LayerStateField> differingFields = mLayers[i].getDifferingFields(*other[i]);
59
60 // If we don't find an approximate match on this layer, then the LayerStacks differ
61 // by too much, so return nothing
62 const int differingFieldCount = __builtin_popcount(differingFields.get());
63 if (differingFieldCount <= kMaxDifferingFields) {
64 approximateMatch = ApproximateMatch{
65 .differingIndex = i,
66 .differingFields = differingFields,
67 };
68 } else {
69 return std::nullopt;
70 }
71 }
72
73 if (approximateMatch) {
74 return approximateMatch;
75 }
76
77 // If we make it through the layer-by-layer comparison without an approximate match,
78 // it means that all layers were either identical or had client-composited layers in common,
79 // which don't affect the composition strategy, so return a successful result with
80 // no differences.
81 return ApproximateMatch{
82 .differingIndex = 0,
83 .differingFields = {},
84 };
85 }
86
fromString(const std::string & string)87 std::optional<Plan> Plan::fromString(const std::string& string) {
88 Plan plan;
89 for (char c : string) {
90 switch (c) {
91 case 'C':
92 plan.addLayerType(hal::Composition::CLIENT);
93 continue;
94 case 'U':
95 plan.addLayerType(hal::Composition::CURSOR);
96 continue;
97 case 'D':
98 plan.addLayerType(hal::Composition::DEVICE);
99 continue;
100 case 'I':
101 plan.addLayerType(hal::Composition::INVALID);
102 continue;
103 case 'B':
104 plan.addLayerType(hal::Composition::SIDEBAND);
105 continue;
106 case 'S':
107 plan.addLayerType(hal::Composition::SOLID_COLOR);
108 continue;
109 default:
110 return std::nullopt;
111 }
112 }
113 return plan;
114 }
115
to_string(const Plan & plan)116 std::string to_string(const Plan& plan) {
117 std::string result;
118 for (auto type : plan.mLayerTypes) {
119 switch (type) {
120 case hal::Composition::CLIENT:
121 result.append("C");
122 break;
123 case hal::Composition::CURSOR:
124 result.append("U");
125 break;
126 case hal::Composition::DEVICE:
127 result.append("D");
128 break;
129 case hal::Composition::INVALID:
130 result.append("I");
131 break;
132 case hal::Composition::SIDEBAND:
133 result.append("B");
134 break;
135 case hal::Composition::SOLID_COLOR:
136 result.append("S");
137 break;
138 }
139 }
140 return result;
141 }
142
dump(std::string & result) const143 void Prediction::dump(std::string& result) const {
144 result.append(to_string(mPlan));
145 result.append(" [Exact ");
146 mExactStats.dump(result);
147 result.append("] [Approximate ");
148 mApproximateStats.dump(result);
149 result.append("]");
150 }
151
getPredictedPlan(const std::vector<const LayerState * > & layers,NonBufferHash hash) const152 std::optional<Predictor::PredictedPlan> Predictor::getPredictedPlan(
153 const std::vector<const LayerState*>& layers, NonBufferHash hash) const {
154 // First check for an exact match
155 if (std::optional<Plan> exactMatch = getExactMatch(hash); exactMatch) {
156 ALOGV("[%s] Found an exact match for %zx", __func__, hash);
157 return PredictedPlan{.hash = hash, .plan = *exactMatch, .type = Prediction::Type::Exact};
158 }
159
160 // If only a hash was passed in for a layer stack with a cached set, don't perform
161 // approximate matches and return early
162 if (layers.empty()) {
163 ALOGV("[%s] Only hash was passed, but no exact match was found", __func__);
164 return std::nullopt;
165 }
166
167 // Then check for approximate matches
168 if (std::optional<NonBufferHash> approximateMatch = getApproximateMatch(layers);
169 approximateMatch) {
170 ALOGV("[%s] Found an approximate match for %zx", __func__, *approximateMatch);
171 const Prediction& prediction = getPrediction(*approximateMatch);
172 return PredictedPlan{.hash = *approximateMatch,
173 .plan = prediction.getPlan(),
174 .type = Prediction::Type::Approximate};
175 }
176
177 return std::nullopt;
178 }
179
recordResult(std::optional<PredictedPlan> predictedPlan,NonBufferHash flattenedHash,const std::vector<const LayerState * > & layers,bool hasSkippedLayers,Plan result)180 void Predictor::recordResult(std::optional<PredictedPlan> predictedPlan,
181 NonBufferHash flattenedHash,
182 const std::vector<const LayerState*>& layers, bool hasSkippedLayers,
183 Plan result) {
184 if (predictedPlan) {
185 recordPredictedResult(*predictedPlan, layers, std::move(result));
186 return;
187 }
188
189 ++mMissCount;
190
191 if (!hasSkippedLayers && findSimilarPrediction(layers, result)) {
192 return;
193 }
194
195 ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash);
196 mCandidates.emplace_front(flattenedHash, Prediction(layers, result));
197 if (mCandidates.size() > MAX_CANDIDATES) {
198 mCandidates.pop_back();
199 }
200 }
201
dump(std::string & result) const202 void Predictor::dump(std::string& result) const {
203 result.append("Predictor state:\n");
204
205 const size_t hitCount = mExactHitCount + mApproximateHitCount;
206 const size_t totalAttempts = hitCount + mMissCount;
207 base::StringAppendF(&result, "Global non-skipped hit rate: %.2f%% (%zd/%zd)\n",
208 100.0f * hitCount / totalAttempts, hitCount, totalAttempts);
209 base::StringAppendF(&result, " Exact hits: %zd\n", mExactHitCount);
210 base::StringAppendF(&result, " Approximate hits: %zd\n", mApproximateHitCount);
211 base::StringAppendF(&result, " Misses: %zd\n\n", mMissCount);
212
213 dumpPredictionsByFrequency(result);
214 }
215
compareLayerStacks(NonBufferHash leftHash,NonBufferHash rightHash,std::string & result) const216 void Predictor::compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash,
217 std::string& result) const {
218 const auto& [leftPredictionEntry, rightPredictionEntry] =
219 std::make_tuple(mPredictions.find(leftHash), mPredictions.find(rightHash));
220 if (leftPredictionEntry == mPredictions.end()) {
221 base::StringAppendF(&result, "No prediction found for %zx\n", leftHash);
222 return;
223 }
224 if (rightPredictionEntry == mPredictions.end()) {
225 base::StringAppendF(&result, "No prediction found for %zx\n", rightHash);
226 return;
227 }
228
229 base::StringAppendF(&result,
230 "Comparing %-16zx %-16zx\n",
231 leftHash, rightHash);
232
233 const auto& [leftPrediction, rightPrediction] =
234 std::make_tuple(leftPredictionEntry->second, rightPredictionEntry->second);
235 const auto& [leftStack, rightStack] = std::make_tuple(leftPrediction.getExampleLayerStack(),
236 rightPrediction.getExampleLayerStack());
237 leftStack.compare(rightStack, result);
238 }
239
describeLayerStack(NonBufferHash hash,std::string & result) const240 void Predictor::describeLayerStack(NonBufferHash hash, std::string& result) const {
241 base::StringAppendF(&result, "Describing %zx:\n\n", hash);
242
243 if (const auto predictionsEntry = mPredictions.find(hash);
244 predictionsEntry != mPredictions.cend()) {
245 const auto& [hash, prediction] = *predictionsEntry;
246
247 prediction.getExampleLayerStack().dump(result);
248
249 result.append("Prediction: ");
250 prediction.dump(result);
251 result.append("\n");
252 } else {
253 result.append("No predictions found\n");
254 }
255 }
256
listSimilarStacks(Plan plan,std::string & result) const257 void Predictor::listSimilarStacks(Plan plan, std::string& result) const {
258 base::StringAppendF(&result, "Similar stacks for plan %s:\n", to_string(plan).c_str());
259
260 if (const auto similarStacksEntry = mSimilarStacks.find(plan);
261 similarStacksEntry != mSimilarStacks.end()) {
262 const auto& [_, similarStacks] = *similarStacksEntry;
263 for (NonBufferHash hash : similarStacks) {
264 base::StringAppendF(&result, "\nPrediction hash %zx:\n", hash);
265 const Prediction& prediction = mPredictions.at(hash);
266 prediction.getExampleLayerStack().dumpLayerNames(result);
267 }
268 } else {
269 result.append("No similar stacks found\n");
270 }
271 }
272
getPrediction(NonBufferHash hash) const273 const Prediction& Predictor::getPrediction(NonBufferHash hash) const {
274 if (const auto predictionEntry = mPredictions.find(hash);
275 predictionEntry != mPredictions.end()) {
276 const auto& [_, prediction] = *predictionEntry;
277 return prediction;
278 } else {
279 const auto candidateEntry = getCandidateEntryByHash(hash);
280 ALOGE_IF(candidateEntry == mCandidates.cend(),
281 "Hash should have been found in either predictions or candidates");
282 const auto& [_, prediction] = *candidateEntry;
283 return prediction;
284 }
285 }
286
getPrediction(NonBufferHash hash)287 Prediction& Predictor::getPrediction(NonBufferHash hash) {
288 return const_cast<Prediction&>(const_cast<const Predictor*>(this)->getPrediction(hash));
289 }
290
getExactMatch(NonBufferHash hash) const291 std::optional<Plan> Predictor::getExactMatch(NonBufferHash hash) const {
292 const Prediction* match = nullptr;
293 if (const auto predictionEntry = mPredictions.find(hash);
294 predictionEntry != mPredictions.end()) {
295 const auto& [hash, prediction] = *predictionEntry;
296 match = &prediction;
297 } else if (const auto candidateEntry = getCandidateEntryByHash(hash);
298 candidateEntry != mCandidates.cend()) {
299 match = &(candidateEntry->prediction);
300 }
301
302 if (match == nullptr) {
303 return std::nullopt;
304 }
305
306 if (match->getMissCount(Prediction::Type::Exact) != 0) {
307 ALOGV("[%s] Skipping exact match for %zx because of prior miss", __func__, hash);
308 return std::nullopt;
309 }
310
311 return match->getPlan();
312 }
313
getApproximateMatch(const std::vector<const LayerState * > & layers) const314 std::optional<NonBufferHash> Predictor::getApproximateMatch(
315 const std::vector<const LayerState*>& layers) const {
316 const auto approximateStackMatches = [&](const ApproximateStack& approximateStack) {
317 const auto& exampleStack = mPredictions.at(approximateStack.hash).getExampleLayerStack();
318 if (const auto approximateMatchOpt = exampleStack.getApproximateMatch(layers);
319 approximateMatchOpt) {
320 return *approximateMatchOpt == approximateStack.match;
321 }
322 return false;
323 };
324
325 const auto candidateMatches = [&](const PromotionCandidate& candidate) {
326 ALOGV("[getApproximateMatch] checking against %zx", candidate.hash);
327 return candidate.prediction.getExampleLayerStack().getApproximateMatch(layers) !=
328 std::nullopt;
329 };
330
331 const Prediction* match = nullptr;
332 NonBufferHash hash;
333 if (const auto approximateStackIter =
334 std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
335 approximateStackMatches);
336 approximateStackIter != mApproximateStacks.cend()) {
337 match = &mPredictions.at(approximateStackIter->hash);
338 hash = approximateStackIter->hash;
339 } else if (const auto candidateEntry =
340 std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
341 candidateEntry != mCandidates.cend()) {
342 match = &(candidateEntry->prediction);
343 hash = candidateEntry->hash;
344 }
345
346 if (match == nullptr) {
347 return std::nullopt;
348 }
349
350 if (match->getMissCount(Prediction::Type::Approximate) != 0) {
351 ALOGV("[%s] Skipping approximate match for %zx because of prior miss", __func__, hash);
352 return std::nullopt;
353 }
354
355 return hash;
356 }
357
promoteIfCandidate(NonBufferHash predictionHash)358 void Predictor::promoteIfCandidate(NonBufferHash predictionHash) {
359 // Return if the candidate has already been promoted
360 if (mPredictions.count(predictionHash) != 0) {
361 return;
362 }
363
364 ALOGV("[%s] Promoting %zx from candidate to prediction", __func__, predictionHash);
365
366 auto candidateEntry = getCandidateEntryByHash(predictionHash);
367 ALOGE_IF(candidateEntry == mCandidates.end(), "Expected to find candidate");
368
369 mSimilarStacks[candidateEntry->prediction.getPlan()].push_back(predictionHash);
370 mPredictions.emplace(predictionHash, std::move(candidateEntry->prediction));
371 mCandidates.erase(candidateEntry);
372 }
373
recordPredictedResult(PredictedPlan predictedPlan,const std::vector<const LayerState * > & layers,Plan result)374 void Predictor::recordPredictedResult(PredictedPlan predictedPlan,
375 const std::vector<const LayerState*>& layers, Plan result) {
376 Prediction& prediction = getPrediction(predictedPlan.hash);
377 if (prediction.getPlan() != result) {
378 ALOGV("[%s] %s prediction missed, expected %s, found %s", __func__,
379 to_string(predictedPlan.type).c_str(), to_string(prediction.getPlan()).c_str(),
380 to_string(result).c_str());
381 prediction.recordMiss(predictedPlan.type);
382 ++mMissCount;
383 return;
384 }
385
386 switch (predictedPlan.type) {
387 case Prediction::Type::Approximate:
388 ++mApproximateHitCount;
389 break;
390 case Prediction::Type::Exact:
391 ++mExactHitCount;
392 break;
393 default:
394 break;
395 }
396
397 ALOGV("[%s] %s prediction hit", __func__, to_string(predictedPlan.type).c_str());
398 ALOGV("[%s] Plan: %s", __func__, to_string(result).c_str());
399 prediction.recordHit(predictedPlan.type);
400
401 const auto stackMatchesHash = [hash = predictedPlan.hash](const ApproximateStack& stack) {
402 return stack.hash == hash;
403 };
404
405 if (predictedPlan.type == Prediction::Type::Approximate) {
406 // If this approximate match is not already in the list of approximate stacks, add it
407 if (std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
408 stackMatchesHash) == mApproximateStacks.cend()) {
409 ALOGV("[%s] Adding approximate match to list", __func__);
410 const auto approximateMatchOpt =
411 prediction.getExampleLayerStack().getApproximateMatch(layers);
412 ALOGE_IF(!approximateMatchOpt, "Expected an approximate match");
413 mApproximateStacks.emplace_back(predictedPlan.hash, *approximateMatchOpt);
414 }
415 }
416
417 promoteIfCandidate(predictedPlan.hash);
418 }
419
findSimilarPrediction(const std::vector<const LayerState * > & layers,Plan result)420 bool Predictor::findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result) {
421 const auto stacksEntry = mSimilarStacks.find(result);
422 if (stacksEntry == mSimilarStacks.end()) {
423 return false;
424 }
425
426 std::optional<ApproximateStack> bestMatch;
427 const auto& [plan, similarStacks] = *stacksEntry;
428 for (NonBufferHash hash : similarStacks) {
429 const Prediction& prediction = mPredictions.at(hash);
430 auto approximateMatch = prediction.getExampleLayerStack().getApproximateMatch(layers);
431 if (!approximateMatch) {
432 continue;
433 }
434
435 const int differingFieldCount = __builtin_popcount(approximateMatch->differingFields.get());
436 if (!bestMatch ||
437 differingFieldCount < __builtin_popcount(bestMatch->match.differingFields.get())) {
438 bestMatch = {hash, *approximateMatch};
439 }
440 }
441
442 if (!bestMatch) {
443 return false;
444 }
445
446 ALOGV("[%s] Adding %zx to approximate stacks", __func__, bestMatch->hash);
447
448 mApproximateStacks.emplace_back(*bestMatch);
449 return true;
450 }
451
dumpPredictionsByFrequency(std::string & result) const452 void Predictor::dumpPredictionsByFrequency(std::string& result) const {
453 struct HashFrequency {
454 HashFrequency(NonBufferHash hash, size_t totalAttempts)
455 : hash(hash), totalAttempts(totalAttempts) {}
456
457 NonBufferHash hash;
458 size_t totalAttempts;
459 };
460
461 std::vector<HashFrequency> hashFrequencies;
462 for (const auto& [hash, prediction] : mPredictions) {
463 hashFrequencies.emplace_back(hash,
464 prediction.getHitCount(Prediction::Type::Total) +
465 prediction.getMissCount(Prediction::Type::Total));
466 }
467
468 std::sort(hashFrequencies.begin(), hashFrequencies.end(),
469 [](const HashFrequency& lhs, const HashFrequency& rhs) {
470 return lhs.totalAttempts > rhs.totalAttempts;
471 });
472
473 result.append("Predictions:\n");
474 for (const auto& [hash, totalAttempts] : hashFrequencies) {
475 base::StringAppendF(&result, " %016zx ", hash);
476 mPredictions.at(hash).dump(result);
477 result.append("\n");
478 }
479 }
480
481 } // namespace android::compositionengine::impl::planner
482