/* * Copyright 2020 The Android Open Source Project * * 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. */ #pragma once #include #include #include #include #include #include #include #include "common/list_map.h" #include "os/log.h" namespace bluetooth { namespace common { // An LRU map-cache the evict the oldest item when reaching capacity // // Usage: // - keys are sorted from warmest to coldest // - iterating through the cache won't warm up keys // - operations on iterators won't warm up keys // - find(), contains(), insert_or_assign() will warm up the key // - insert_or_assign() will evict coldest key when cache reaches capacity // - NOT THREAD SAFE // // Performance: // - Key look-up and modification is O(1) // - Memory consumption is: // O(2*capacity*sizeof(K) + capacity*(sizeof(nullptr)+sizeof(V))) // // Template: // - Key key type // - T value type // */ template class LruCache { public: using value_type = typename ListMap::value_type; // different from c++17 node_type on purpose as we want node to be copyable using node_type = typename ListMap::node_type; using iterator = typename ListMap::iterator; using const_iterator = typename ListMap::const_iterator; // Constructor a LRU cache with |capacity| explicit LruCache(size_t capacity) : capacity_(capacity) { ASSERT_LOG(capacity_ != 0, "Unable to have 0 LRU Cache capacity"); } // for move LruCache(LruCache&& other) noexcept = default; LruCache& operator=(LruCache&& other) noexcept = default; // copy-constructor // iterators in key_map_ cannot be copied directly LruCache(const LruCache& other) : capacity_(other.capacity_), list_map_(other.list_map_) {} // copy-assignment // iterators in key_map_ cannot be copied directly LruCache& operator=(const LruCache& other) { if (&other == this) { return *this; } capacity_ = other.capacity_; list_map_ = other.list_map_; return *this; } // comparison operators bool operator==(const LruCache& rhs) const { return capacity_ == rhs.capacity_ && list_map_ == rhs.list_map_; } bool operator!=(const LruCache& rhs) const { return !(*this == rhs); } ~LruCache() { clear(); } // Clear the cache void clear() { list_map_.clear(); } // Find the value of a key, and move the key to the head of cache, if there is one. Return iterator to value if key // exists, end() if not. Iterator might be invalidated when removed or evicted. Const version. // // LRU: Will warm up key // LRU: Access to returned iterator won't move key in LRU const_iterator find(const Key& key) const { return const_cast(this)->find(key); } // Find the value of a key, and move the key to the head of cache, if there is one. Return iterator to value if key // exists, end() if not. Iterator might be invalidated when removed or evicted // // LRU: Will warm up key // LRU: Access to returned iterator won't move key in LRU iterator find(const Key& key) { auto iter = list_map_.find(key); if (iter == list_map_.end()) { return end(); } // move to front list_map_.splice(list_map_.begin(), list_map_, iter); return iter; } // Check if key exist in the cache. Return true if key exist in cache, false, if not // // LRU: Will warm up key bool contains(const Key& key) const { return find(key) != list_map_.end(); } // Put a key-value pair to the head of cache, evict the oldest key if cache is at capacity. Eviction is based on key // ONLY. Hence, updating a key will not evict the oldest key. Return evicted value if old value was evicted, // std::nullopt if not. The return value will be evaluated to true in a boolean context if a value is contained by // std::optional, false otherwise. // // LRU: Will warm up key std::optional insert_or_assign(const Key& key, T value) { if (contains(key)) { // contains() calls find() that moved the node to the head list_map_.begin()->second = std::move(value); return std::nullopt; } // remove tail if at capacity std::optional evicted_node = std::nullopt; if (list_map_.size() == capacity_) { evicted_node = list_map_.extract(std::prev(list_map_.end())->first); } // insert new one to front of list list_map_.insert_or_assign(list_map_.begin(), key, std::move(value)); return evicted_node; } // Put a key-value pair to the head of cache, evict the oldest key if cache is at capacity. Eviction is based on key // ONLY. Hence, updating a key will not evict the oldest key. This method tries to construct the value in-place. If // the key already exist, this method only update the value. Return inserted iterator, whether insertion happens, and // evicted value if old value was evicted or std::nullopt // // LRU: Will warm up key template std::tuple> try_emplace(const Key& key, Args&&... args) { if (contains(key)) { // contains() calls find() that moved the node to the head return std::make_tuple(end(), false, std::nullopt); } // remove tail if at capacity std::optional evicted_node = std::nullopt; if (list_map_.size() == capacity_) { evicted_node = list_map_.extract(std::prev(list_map_.end())->first); } // insert new one to front of list auto pair = list_map_.try_emplace(list_map_.begin(), key, std::forward(args)...); return std::make_tuple(pair.first, pair.second, std::move(evicted_node)); } // Delete a key from cache, return removed value if old value was evicted, std::nullopt if not. The return value will // be evaluated to true in a boolean context if a value is contained by std::optional, false otherwise. inline std::optional extract(const Key& key) { return list_map_.extract(key); } /// Remove an iterator pointed item from the lru cache and return the iterator immediately after the erased item iterator erase(const_iterator iter) { return list_map_.erase(iter); } // Return size of the cache inline size_t size() const { return list_map_.size(); } // Iterator interface for begin inline iterator begin() { return list_map_.begin(); } // Return iterator interface for begin, const inline const_iterator begin() const { return list_map_.begin(); } // Return iterator interface for end inline iterator end() { return list_map_.end(); } // Iterator interface for end, const inline const_iterator end() const { return list_map_.end(); } private: size_t capacity_; ListMap list_map_; }; } // namespace common } // namespace bluetooth