Skip to content

Commit c54dc01

Browse files
committed
factored ordered hash map code
Signed-off-by: wadeking98 <[email protected]>
1 parent cb79261 commit c54dc01

File tree

1 file changed

+149
-117
lines changed

1 file changed

+149
-117
lines changed

libindy_vdr/src/pool/cache.rs

Lines changed: 149 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::{
33
collections::{BTreeMap, HashMap},
44
fmt::Debug,
55
hash::Hash,
6-
ops::DerefMut,
76
sync::Arc,
87
time::SystemTime,
98
};
@@ -49,10 +48,120 @@ impl<K, V> Clone for Cache<K, V> {
4948
}
5049
}
5150

51+
/// A hashmap that also maintains a BTreeMap of keys ordered by a given value
52+
/// This is useful for structures that need fast O(1) lookups, but also need to evict the oldest or least recently used entries
53+
struct OrderedHashMap<K, O, V>((HashMap<K, (O, V)>, BTreeMap<O, Vec<K>>));
54+
55+
impl<K, O, V> OrderedHashMap<K, O, V> {
56+
fn new() -> Self {
57+
Self((HashMap::new(), BTreeMap::new()))
58+
}
59+
}
60+
61+
impl<K: Hash + Eq + Clone, O: Ord + Copy, V> OrderedHashMap<K, O, V> {
62+
fn len(&self) -> usize {
63+
let (lookup, _) = &self.0;
64+
lookup.len()
65+
}
66+
fn get(&self, key: &K) -> Option<&(O, V)> {
67+
let (lookup, _) = &self.0;
68+
lookup.get(key)
69+
}
70+
fn get_key_value(
71+
&self,
72+
selector: Box<dyn Fn(&BTreeMap<O, Vec<K>>) -> Option<(&O, &Vec<K>)>>,
73+
) -> Option<(&K, &O, &V)> {
74+
let (lookup, ordered_lookup) = &self.0;
75+
selector(ordered_lookup).and_then(|(_, keys)| {
76+
keys.first()
77+
.and_then(|key| lookup.get(key).and_then(|(o, v)| Some((key, o, v))))
78+
})
79+
}
80+
/// gets the entry with the lowest order value
81+
fn get_first_key_value(&self) -> Option<(&K, &O, &V)> {
82+
self.get_key_value(Box::new(|ordered_lookup| ordered_lookup.first_key_value()))
83+
}
84+
/// gets the entry with the highest order value
85+
fn get_last_key_value(&self) -> Option<(&K, &O, &V)> {
86+
self.get_key_value(Box::new(|ordered_lookup| ordered_lookup.last_key_value()))
87+
}
88+
/// re-orders the entry with the given key
89+
fn re_order(&mut self, key: &K, new_order: O) {
90+
let (lookup, order_lookup) = &mut self.0;
91+
if let Some((old_order, _)) = lookup.get(key) {
92+
// remove entry in btree
93+
match order_lookup.get_mut(old_order) {
94+
Some(keys) => {
95+
keys.retain(|k| k != key);
96+
if keys.len() == 0 {
97+
order_lookup.remove(old_order);
98+
}
99+
}
100+
None => {}
101+
}
102+
}
103+
order_lookup
104+
.entry(new_order)
105+
.or_insert(vec![])
106+
.push(key.clone());
107+
lookup.get_mut(key).map(|(o, _)| *o = new_order);
108+
}
109+
/// inserts a new entry with the given key and value and order
110+
fn insert(&mut self, key: K, value: V, order: O) -> Option<V> {
111+
let (lookup, order_lookup) = &mut self.0;
112+
113+
if let Some((old_order, _)) = lookup.get(&key) {
114+
// remove entry in btree
115+
match order_lookup.get_mut(old_order) {
116+
Some(keys) => {
117+
keys.retain(|k| k != &key);
118+
if keys.len() == 0 {
119+
order_lookup.remove(old_order);
120+
}
121+
}
122+
None => {}
123+
}
124+
}
125+
order_lookup
126+
.entry(order)
127+
.or_insert(vec![])
128+
.push(key.clone());
129+
lookup
130+
.insert(key, (order, value))
131+
.and_then(|(_, v)| Some(v))
132+
}
133+
/// removes the entry with the given key
134+
fn remove(&mut self, key: &K) -> Option<(O, V)> {
135+
let (lookup, order_lookup) = &mut self.0;
136+
lookup.remove(key).and_then(|(order, v)| {
137+
match order_lookup.get_mut(&order) {
138+
Some(keys) => {
139+
keys.retain(|k| k != key);
140+
if keys.len() == 0 {
141+
order_lookup.remove(&order);
142+
}
143+
}
144+
None => {}
145+
}
146+
Some((order, v))
147+
})
148+
}
149+
/// removes the entry with the lowest order value
150+
fn remove_first(&mut self) -> Option<(K, O, V)> {
151+
let first_key = self.get_first_key_value().map(|(k, _, _)| k.clone());
152+
if let Some(first_key) = first_key {
153+
self.remove(&first_key)
154+
.map(|(order, v)| (first_key, order, v))
155+
} else {
156+
None
157+
}
158+
}
159+
}
160+
52161
/// A simple in-memory cache that uses timestamps to expire entries. Once the cache fills up, the oldest entry is evicted.
53162
/// Uses a hashmap for lookups and a BTreeMap for ordering by age
54163
pub struct MemCacheStorageTTL<K, V> {
55-
store: (HashMap<K, (V, u128)>, BTreeMap<u128, Vec<K>>),
164+
store: OrderedHashMap<K, u128, V>,
56165
capacity: usize,
57166
startup_time: SystemTime,
58167
expire_after: u128,
@@ -62,28 +171,21 @@ impl<K, V> MemCacheStorageTTL<K, V> {
62171
/// Create a new cache with the given capacity and expiration time in milliseconds
63172
pub fn new(capacity: usize, expire_after: u128) -> Self {
64173
Self {
65-
store: (HashMap::new(), BTreeMap::new()),
174+
store: OrderedHashMap::new(),
66175
capacity,
67176
startup_time: SystemTime::now(),
68177
expire_after,
69178
}
70179
}
71-
fn get_oldest_ts(cache_order: &mut BTreeMap<u128, Vec<K>>) -> u128 {
72-
match cache_order.first_key_value() {
73-
Some((oldest_ts_ref, _)) => *oldest_ts_ref,
74-
None => 0,
75-
}
76-
}
77180
}
78181

79182
#[async_trait]
80183
impl<K: Hash + Eq + Send + Sync + 'static + Clone + Debug, V: Clone + Send + Sync + 'static>
81184
CacheStorage<K, V> for MemCacheStorageTTL<K, V>
82185
{
83186
async fn get(&self, key: &K) -> Option<V> {
84-
let (cache_lookup, _) = &self.store;
85-
match cache_lookup.get(key) {
86-
Some((v, ts)) => {
187+
match self.store.get(key) {
188+
Some((ts, v)) => {
87189
let current_time = SystemTime::now()
88190
.duration_since(self.startup_time)
89191
.unwrap()
@@ -98,96 +200,51 @@ impl<K: Hash + Eq + Send + Sync + 'static + Clone + Debug, V: Clone + Send + Syn
98200
}
99201
}
100202
async fn remove(&mut self, key: &K) -> Option<V> {
101-
let (cache_lookup, cache_order) = &mut self.store;
102-
let ttl_val = cache_lookup.remove(key);
103-
match ttl_val {
104-
Some((v, ts)) => {
105-
let val = cache_order.get_mut(&ts).unwrap();
106-
if val.len() <= 1 {
107-
cache_order.remove(&ts);
108-
} else {
109-
val.retain(|k| k != key);
110-
}
111-
Some(v)
112-
}
113-
None => None,
114-
}
203+
self.store.remove(key).map(|(_, v)| v)
115204
}
116205
async fn insert(&mut self, key: K, value: V) -> Option<V> {
117-
let (cache_lookup, cache_order) = &mut self.store;
118-
let ts = SystemTime::now()
206+
let current_ts = SystemTime::now()
119207
.duration_since(self.startup_time)
120208
.unwrap()
121209
.as_millis();
122210

123211
// remove expired entries
124-
while cache_order.len() > 0 {
125-
let oldest_ts = Self::get_oldest_ts(cache_order);
126-
if ts > oldest_ts + self.expire_after {
127-
let expired_keys = cache_order.get(&oldest_ts).unwrap();
128-
for key in expired_keys.iter() {
129-
println!(
130-
"removing expired key: {:?}, exp_time {:?}, oldest entry date {:?}",
131-
key,
132-
ts + self.expire_after,
133-
oldest_ts
134-
);
135-
cache_lookup.remove(key);
136-
}
137-
cache_order.remove(&oldest_ts);
138-
} else {
139-
break;
140-
}
212+
let exp_offset = self.expire_after;
213+
while self.store.len() > 0
214+
&& self
215+
.store
216+
.get_first_key_value()
217+
.map(|(_, ts, _)| ts + exp_offset < current_ts)
218+
.unwrap_or(false)
219+
{
220+
self.store.remove_first();
141221
}
142222

143223
// remove the oldest item if the cache is still full
144-
if cache_lookup.len() >= self.capacity && cache_lookup.get(&key).is_none() {
224+
if self.store.len() >= self.capacity && self.store.get(&key).is_none() {
145225
// remove the oldest item
146-
let oldest_ts = Self::get_oldest_ts(cache_order);
147-
let oldest_keys = cache_order.get_mut(&oldest_ts).unwrap();
148-
let removal_key = oldest_keys.first().and_then(|k| Some(k.clone()));
149-
if oldest_keys.len() <= 1 {
150-
// remove the whole array since it's the last entry
151-
cache_order.remove(&oldest_ts);
152-
} else {
153-
oldest_keys.swap_remove(0);
154-
}
155-
cache_lookup.remove(&key);
226+
let removal_key = self.store.get_first_key_value().map(|(k, _, _)| k.clone());
156227
if let Some(removal_key) = removal_key {
157-
cache_lookup.remove(&removal_key);
228+
self.store.remove(&removal_key);
158229
}
159230
};
160231

161-
// if value is overwritten when inserting a new key, we need to remove the old key from the order index
162-
cache_order.entry(ts).or_insert(vec![]).push(key.clone());
163-
match cache_lookup.insert(key.clone(), (value.clone(), ts)) {
164-
Some((v, ts)) => {
165-
if let Some(ord_keys) = cache_order.get_mut(&ts) {
166-
if ord_keys.len() <= 1 {
167-
cache_order.remove(&ts);
168-
} else {
169-
ord_keys.retain(|k| k != &key);
170-
}
171-
}
172-
Some(v)
173-
}
174-
None => None,
175-
}
232+
self.store.insert(key, value, current_ts)
176233
}
177234
}
178235

179236
/// A simple in-memory LRU cache. Once the cache fills up, the least recently used entry is evicted.
180237
/// Uses a hashmap for lookups and a BTreeMap for ordering by least recently used
181238
pub struct MemCacheStorageLRU<K, V> {
182239
// The store is wrapped in an arc and a mutex so that get() can be immutable
183-
store: Arc<Mutex<(HashMap<K, (V, u64)>, BTreeMap<u64, K>)>>,
240+
store: Arc<Mutex<OrderedHashMap<K, u64, V>>>,
184241
capacity: usize,
185242
}
186243

187244
impl<K, V> MemCacheStorageLRU<K, V> {
188245
pub fn new(capacity: usize) -> Self {
189246
Self {
190-
store: Arc::new(Mutex::new((HashMap::new(), BTreeMap::new()))),
247+
store: Arc::new(Mutex::new(OrderedHashMap::new())),
191248
capacity,
192249
}
193250
}
@@ -198,62 +255,37 @@ impl<K: Hash + Eq + Send + Sync + 'static + Clone, V: Clone + Send + Sync + 'sta
198255
{
199256
async fn get(&self, key: &K) -> Option<V> {
200257
// move the key to the end of the LRU index
201-
// this is O(log(n)) in the worst case, but in the average case it's close to O(1)
258+
// this is O(log(n))
202259
let mut store_lock = self.store.lock().await;
203-
let (cache_lookup, cache_order) = store_lock.deref_mut();
204-
let highest_lru = cache_order
205-
.last_key_value()
206-
.map(|(hts, _)| hts + 1)
260+
let highest_lru = store_lock
261+
.get_last_key_value()
262+
.map(|(_, ts, _)| ts + 1)
207263
.unwrap_or(0);
208-
match cache_lookup.get_mut(key) {
209-
Some((v, ts)) => {
210-
cache_order.remove(ts).unwrap();
211-
cache_order.entry(highest_lru).or_insert(key.clone());
212-
*ts = highest_lru;
213-
Some(v.clone())
214-
}
215-
None => None,
216-
}
264+
store_lock.re_order(key, highest_lru);
265+
store_lock.get(key).map(|(_, v)| v.clone())
217266
}
218267
async fn remove(&mut self, key: &K) -> Option<V> {
219268
let mut store_lock = self.store.lock().await;
220-
let (cache_lookup, cache_order) = store_lock.deref_mut();
221-
let lru_val = cache_lookup.remove(key);
222-
match lru_val {
223-
Some((v, ts)) => {
224-
cache_order.remove(&ts);
225-
Some(v)
226-
}
227-
None => None,
228-
}
269+
store_lock.remove(key).map(|(_, v)| v)
229270
}
230271
async fn insert(&mut self, key: K, value: V) -> Option<V> {
231272
// this will be O(log(n)) in all cases when cache is at capacity since we need to fetch the first and last element from the btree
232273
let mut store_lock = self.store.lock().await;
233-
let (cache_lookup, cache_order) = store_lock.deref_mut();
234-
let highest_lru = cache_order
235-
.last_key_value()
236-
.map(|(ts, _)| ts + 1)
274+
let highest_lru = store_lock
275+
.get_last_key_value()
276+
.map(|(_, ts, _)| ts + 1)
237277
.unwrap_or(0);
238-
if cache_lookup.len() >= self.capacity && cache_lookup.get(&key).is_none() {
278+
279+
if store_lock.len() >= self.capacity && store_lock.get(&key).is_none() {
239280
// remove the LRU item
240-
let (lru_ts, lru_key) = match cache_order.first_key_value() {
241-
Some((ts, key)) => (*ts, key.clone()),
242-
None => return None,
243-
};
244-
cache_lookup.remove(&lru_key);
245-
cache_order.remove(&lru_ts);
281+
let lru_key = store_lock
282+
.get_first_key_value()
283+
.map(|(k, _, _)| k.clone())
284+
.unwrap();
285+
store_lock.remove(&lru_key);
246286
};
247287

248-
// if value is overwritten when inserting a new key, we need to remove the old key from the order index
249-
cache_order.insert(highest_lru, key.clone());
250-
match cache_lookup.insert(key.clone(), (value.clone(), highest_lru)) {
251-
Some((v, ts)) => {
252-
cache_order.remove(&ts);
253-
Some(v)
254-
}
255-
None => None,
256-
}
288+
store_lock.insert(key, value, highest_lru)
257289
}
258290
}
259291

0 commit comments

Comments
 (0)