// Copyright 2019 The MediaPipe Authors. // // 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. #ifndef MEDIAPIPE_UTIL_RESOURCE_CACHE_H_ #define MEDIAPIPE_UTIL_RESOURCE_CACHE_H_ #include #include "absl/container/flat_hash_map.h" #include "absl/functional/function_ref.h" #include "mediapipe/framework/port/logging.h" namespace mediapipe { // Maintains a cache for resources of type `Value`, where the type of the // resource (e.g., image dimension for an image pool) is described bye the `Key` // type. The `Value` type must include an unset value, with implicit conversion // to bool reflecting set/unset state. template ::hasher> class ResourceCache { public: Value Lookup( const Key& key, absl::FunctionRef create) { auto map_it = map_.find(key); Entry* entry; if (map_it == map_.end()) { std::tie(map_it, std::ignore) = map_.try_emplace(key, std::make_unique(key)); entry = map_it->second.get(); CHECK_EQ(entry->request_count, 0); entry->request_count = 1; entry_list_.Append(entry); if (entry->prev != nullptr) CHECK_GE(entry->prev->request_count, 1); } else { entry = map_it->second.get(); ++entry->request_count; Entry* larger = entry->prev; while (larger != nullptr && larger->request_count < entry->request_count) { larger = larger->prev; } if (larger != entry->prev) { entry_list_.Remove(entry); entry_list_.InsertAfter(entry, larger); } } if (!entry->value) { entry->value = create(entry->key, entry->request_count); } ++total_request_count_; return entry->value; } std::vector Evict(int max_count, int request_count_scrub_interval) { std::vector evicted; // Remove excess entries. while (entry_list_.size() > max_count) { Entry* victim = entry_list_.tail(); evicted.emplace_back(std::move(victim->value)); entry_list_.Remove(victim); map_.erase(victim->key); } // Every request_count_scrub_interval, halve the request counts, and // remove entries which have fallen to 0. // This keeps sporadic requests from accumulating and eventually exceeding // the minimum request threshold for allocating a pool. Also, it means that // if the request regimen changes (e.g. a graph was always requesting a // large size, but then switches to a small size to save memory or CPU), the // pool can quickly adapt to it. bool scrub = total_request_count_ >= request_count_scrub_interval; if (scrub) { total_request_count_ = 0; for (Entry* entry = entry_list_.head(); entry != nullptr;) { entry->request_count /= 2; Entry* next = entry->next; if (entry->request_count == 0) { evicted.emplace_back(std::move(entry->value)); entry_list_.Remove(entry); map_.erase(entry->key); } entry = next; } } return evicted; } private: struct Entry { Entry(const Key& key) : key(key) {} Entry* prev = nullptr; Entry* next = nullptr; int request_count = 0; Key key; Value value; }; // Unlike std::list, this is an intrusive list, meaning that the prev and next // pointers live inside the element. Apart from not requiring an extra // allocation, this means that once we look up an entry by key in the pools_ // map we do not need to look it up separately in the list. // class EntryList { public: void Prepend(Entry* entry) { if (head_ == nullptr) { head_ = tail_ = entry; } else { entry->next = head_; head_->prev = entry; head_ = entry; } ++size_; } void Append(Entry* entry) { if (tail_ == nullptr) { head_ = tail_ = entry; } else { tail_->next = entry; entry->prev = tail_; tail_ = entry; } ++size_; } void Remove(Entry* entry) { if (entry == head_) { head_ = entry->next; } else { entry->prev->next = entry->next; } if (entry == tail_) { tail_ = entry->prev; } else { entry->next->prev = entry->prev; } entry->prev = nullptr; entry->next = nullptr; --size_; } void InsertAfter(Entry* entry, Entry* after) { if (after != nullptr) { entry->next = after->next; if (entry->next) entry->next->prev = entry; entry->prev = after; after->next = entry; ++size_; } else { Prepend(entry); } } Entry* head() { return head_; } Entry* tail() { return tail_; } size_t size() { return size_; } private: Entry* head_ = nullptr; Entry* tail_ = nullptr; size_t size_ = 0; }; absl::flat_hash_map, KeyHash> map_; EntryList entry_list_; int total_request_count_ = 0; }; } // namespace mediapipe #endif // MEDIAPIPE_UTIL_RESOURCE_CACHE_H_