a67069156e
This is the recommended hashmap in most cases. PiperOrigin-RevId: 488772031
183 lines
5.5 KiB
C++
183 lines
5.5 KiB
C++
// 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 <unordered_map>
|
|
|
|
#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 <typename Key, typename Value,
|
|
typename KeyHash = typename absl::flat_hash_map<Key, int>::hasher>
|
|
class ResourceCache {
|
|
public:
|
|
Value Lookup(
|
|
const Key& key,
|
|
absl::FunctionRef<Value(const Key& key, int request_count)> 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<Entry>(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<Value> Evict(int max_count, int request_count_scrub_interval) {
|
|
std::vector<Value> 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<Key, std::unique_ptr<Entry>, KeyHash> map_;
|
|
EntryList entry_list_;
|
|
int total_request_count_ = 0;
|
|
};
|
|
|
|
} // namespace mediapipe
|
|
|
|
#endif // MEDIAPIPE_UTIL_RESOURCE_CACHE_H_
|