Skip to content

Commit 87bc732

Browse files
committed
(XXX: Bench + test) Swap IndexedMap implementation for a HashMap+B-Tree
Our network graph has to be iterable in a deterministic order and with the ability to iterate over a specific range. Thus, historically, we've used a `BTreeMap` to do the iteration. This is fine, except our map needs to also provide high performance lookups in order to make route-finding fast. Sadly, `BTreeMap`s are quite slow due to the branching penalty. Here we replace the implementation of our `IndexedMap` with a `HashMap` to store the elements itself and a `BTreeSet` to store the keys set in sorted order for iteration.
1 parent fc7ef8e commit 87bc732

File tree

1 file changed

+60
-22
lines changed

1 file changed

+60
-22
lines changed

lightning/src/util/indexed_map.rs

+60-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use alloc::collections::{BTreeMap, btree_map};
1+
use crate::prelude::{HashMap, hash_map};
2+
use alloc::collections::{BTreeSet, btree_set};
3+
use core::hash::Hash;
24
use core::cmp::Ord;
35
use core::ops::RangeBounds;
46

@@ -18,15 +20,18 @@ use core::ops::RangeBounds;
1820
/// actually backed by a `HashMap`, with some additional tracking to ensure we can iterate over
1921
/// keys in the order defined by [`Ord`].
2022
#[derive(Clone, PartialEq, Eq)]
21-
pub struct IndexedMap<K: Ord, V> {
22-
map: BTreeMap<K, V>,
23+
pub struct IndexedMap<K: Hash + Ord, V> {
24+
map: HashMap<K, V>,
25+
// TODO: Explore swapping this for a sorted vec (that is only sorted on first range() call)
26+
keys: BTreeSet<K>,
2327
}
2428

25-
impl<K: Ord, V> IndexedMap<K, V> {
29+
impl<K: Clone + Hash + Ord, V> IndexedMap<K, V> {
2630
/// Constructs a new, empty map
2731
pub fn new() -> Self {
2832
Self {
29-
map: BTreeMap::new(),
33+
map: HashMap::new(),
34+
keys: BTreeSet::new(),
3035
}
3136
}
3237

@@ -49,26 +54,37 @@ impl<K: Ord, V> IndexedMap<K, V> {
4954

5055
/// Removes the element with the given `key`, returning it, if one exists.
5156
pub fn remove(&mut self, key: &K) -> Option<V> {
52-
self.map.remove(key)
57+
let ret = self.map.remove(key);
58+
if let Some(_) = ret {
59+
assert!(self.keys.remove(key), "map and keys must be consistent");
60+
}
61+
ret
5362
}
5463

5564
/// Inserts the given `key`/`value` pair into the map, returning the element that was
5665
/// previously stored at the given `key`, if one exists.
5766
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
58-
self.map.insert(key, value)
67+
let ret = self.map.insert(key.clone(), value);
68+
if ret.is_none() {
69+
assert!(self.keys.insert(key), "map and keys must be consistent");
70+
}
71+
ret
5972
}
6073

6174
/// Returns an [`Entry`] for the given `key` in the map, allowing access to the value.
6275
pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
63-
match self.map.entry(key) {
64-
btree_map::Entry::Vacant(entry) => {
76+
match self.map.entry(key.clone()) {
77+
hash_map::Entry::Vacant(entry) => {
6578
Entry::Vacant(VacantEntry {
66-
underlying_entry: entry
79+
underlying_entry: entry,
80+
key,
81+
keys: &mut self.keys,
6782
})
6883
},
69-
btree_map::Entry::Occupied(entry) => {
84+
hash_map::Entry::Occupied(entry) => {
7085
Entry::Occupied(OccupiedEntry {
71-
underlying_entry: entry
86+
underlying_entry: entry,
87+
keys: &mut self.keys,
7288
})
7389
}
7490
}
@@ -91,8 +107,11 @@ impl<K: Ord, V> IndexedMap<K, V> {
91107
}
92108

93109
/// Returns an iterator which iterates over the `key`/`value` pairs in a given range.
94-
pub fn range<R: RangeBounds<K>>(&self, range: R) -> btree_map::Range<K, V> {
95-
self.map.range(range)
110+
pub fn range<R: RangeBounds<K>>(&self, range: R) -> Range<K, V> {
111+
Range {
112+
inner_range: self.keys.range(range),
113+
map: &self.map,
114+
}
96115
}
97116

98117
/// Returns the number of `key`/`value` pairs in the map
@@ -106,34 +125,53 @@ impl<K: Ord, V> IndexedMap<K, V> {
106125
}
107126
}
108127

128+
pub struct Range<'a, K: Hash + Ord, V> {
129+
inner_range: btree_set::Range<'a, K>,
130+
map: &'a HashMap<K, V>,
131+
}
132+
impl<'a, K: Hash + Ord, V: 'a> Iterator for Range<'a, K, V> {
133+
type Item = (&'a K, &'a V);
134+
fn next(&mut self) -> Option<(&'a K, &'a V)> {
135+
self.inner_range.next().map(|k| {
136+
(k, self.map.get(k).expect("map and keys must be consistent"))
137+
})
138+
}
139+
}
140+
109141
/// An [`Entry`] for a key which currently has no value
110-
pub struct VacantEntry<'a, K: Ord, V> {
111-
underlying_entry: btree_map::VacantEntry<'a, K, V>,
142+
pub struct VacantEntry<'a, K: Hash + Ord, V> {
143+
underlying_entry: hash_map::VacantEntry<'a, K, V>,
144+
key: K,
145+
keys: &'a mut BTreeSet<K>,
112146
}
113147

114148
/// An [`Entry`] for an existing key-value pair
115-
pub struct OccupiedEntry<'a, K: Ord, V> {
116-
underlying_entry: btree_map::OccupiedEntry<'a, K, V>,
149+
pub struct OccupiedEntry<'a, K: Hash + Ord, V> {
150+
underlying_entry: hash_map::OccupiedEntry<'a, K, V>,
151+
keys: &'a mut BTreeSet<K>,
117152
}
118153

119154
/// A mutable reference to a position in the map. This can be used to reference, add, or update the
120155
/// value at a fixed key.
121-
pub enum Entry<'a, K: Ord, V> {
156+
pub enum Entry<'a, K: Hash + Ord, V> {
122157
Vacant(VacantEntry<'a, K, V>),
123158
Occupied(OccupiedEntry<'a, K, V>),
124159
}
125160

126-
impl<'a, K: Ord, V> VacantEntry<'a, K, V> {
161+
impl<'a, K: Hash + Ord, V> VacantEntry<'a, K, V> {
127162
/// Insert a value into the position described by this entry.
128163
pub fn insert(self, value: V) -> &'a mut V {
164+
assert!(self.keys.insert(self.key), "map and keys must be consistent");
129165
self.underlying_entry.insert(value)
130166
}
131167
}
132168

133-
impl<'a, K: Ord, V> OccupiedEntry<'a, K, V> {
169+
impl<'a, K: Hash + Ord, V> OccupiedEntry<'a, K, V> {
134170
/// Remove the value at the position described by this entry.
135171
pub fn remove_entry(self) -> (K, V) {
136-
self.underlying_entry.remove_entry()
172+
let res = self.underlying_entry.remove_entry();
173+
assert!(self.keys.remove(&res.0), "map and keys must be consistent");
174+
res
137175
}
138176

139177
/// Get a reference to the value at the position described by this entry.

0 commit comments

Comments
 (0)