• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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