Skip to content

Commit e041939

Browse files
committed
Improve LibMappings::add_mapping performance by using a BTreeMap.
I was seeing performance problems when using LibMappings for JIT mappings, where there is an individual mapping for every JIT function. It was spending a long time inserting into the middle of a sorted Vec. Use a BTreeMap instead.
1 parent e16caa6 commit e041939

File tree

1 file changed

+75
-51
lines changed

1 file changed

+75
-51
lines changed
Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
use std::collections::BTreeMap;
2+
13
/// Keeps track of mapped libraries in an address space. Stores a value
24
/// for each mapping, and allows efficient lookup of that value based on
35
/// an address.
6+
///
7+
/// A "library" here is a loose term; it could be a normal shared library,
8+
/// or the main binary, but it could also be a synthetic library for JIT
9+
/// code. For normal libraries, there's usually just one mapping per library.
10+
/// For JIT code, you could have many small mappings, one per JIT function,
11+
/// all pointing to the synthetic JIT "library".
412
#[derive(Debug, Clone)]
513
pub struct LibMappings<T> {
6-
sorted_mappings: Vec<Mapping<T>>,
14+
/// A BTreeMap of non-overlapping Mappings. The key is the start_avma of the mapping.
15+
///
16+
/// When a new mapping is added, overlapping mappings are removed.
17+
map: BTreeMap<u64, Mapping<T>>,
718
}
819

920
impl<T> Default for LibMappings<T> {
@@ -16,7 +27,7 @@ impl<T> LibMappings<T> {
1627
/// Creates a new empty instance.
1728
pub fn new() -> Self {
1829
Self {
19-
sorted_mappings: Vec::new(),
30+
map: BTreeMap::new(),
2031
}
2132
}
2233

@@ -48,80 +59,68 @@ impl<T> LibMappings<T> {
4859
relative_address_at_start: u32,
4960
value: T,
5061
) {
51-
let remove_range_begin = match self
52-
.sorted_mappings
53-
.binary_search_by_key(&start_avma, |r| r.start_avma)
54-
{
55-
Ok(i) => i,
56-
Err(0) => 0,
57-
Err(i) => {
58-
// start_avma falls between the start_avmas of `i - 1` and `i`.
59-
if start_avma < self.sorted_mappings[i - 1].end_avma {
60-
i - 1
61-
} else {
62-
i
63-
}
64-
}
65-
};
66-
67-
let mut remove_range_end = remove_range_begin;
68-
for mapping in &self.sorted_mappings[remove_range_begin..] {
69-
if mapping.start_avma < end_avma {
70-
remove_range_end += 1;
62+
let removal_avma_range_start =
63+
if let Some(mapping_overlapping_with_start_avma) = self.lookup_impl(start_avma) {
64+
mapping_overlapping_with_start_avma.start_avma
7165
} else {
72-
break;
73-
}
66+
start_avma
67+
};
68+
// self.map.drain(removal_avma_range_start..end_avma);
69+
let overlapping_keys: Vec<u64> = self
70+
.map
71+
.range(removal_avma_range_start..end_avma)
72+
.map(|(start_avma, _)| *start_avma)
73+
.collect();
74+
for key in overlapping_keys {
75+
self.map.remove(&key);
7476
}
7577

76-
self.sorted_mappings.splice(
77-
remove_range_begin..remove_range_end,
78-
[Mapping {
78+
self.map.insert(
79+
start_avma,
80+
Mapping {
7981
start_avma,
8082
end_avma,
8183
relative_address_at_start,
8284
value,
83-
}],
85+
},
8486
);
8587
}
8688

8789
/// Remove a mapping which starts at the given address. If found, this returns
8890
/// the `relative_address_at_start` and the associated value of the mapping.
8991
pub fn remove_mapping(&mut self, start_avma: u64) -> Option<(u32, T)> {
90-
self.sorted_mappings
91-
.binary_search_by_key(&start_avma, |m| m.start_avma)
92-
.ok()
93-
.map(|i| self.sorted_mappings.remove(i))
92+
self.map
93+
.remove(&start_avma)
9494
.map(|m| (m.relative_address_at_start, m.value))
9595
}
9696

9797
/// Clear all mappings.
9898
pub fn clear(&mut self) {
99-
self.sorted_mappings.clear();
100-
self.sorted_mappings.shrink_to_fit();
99+
self.map.clear();
101100
}
102101

103-
/// Look up the mapping which covers the given address.
104-
fn lookup(&self, avma: u64) -> Option<&Mapping<T>> {
105-
let mappings = &self.sorted_mappings[..];
106-
let index = match mappings.binary_search_by_key(&avma, |r| r.start_avma) {
107-
Err(0) => return None,
108-
Ok(exact_match) => exact_match,
109-
Err(insertion_index) => {
110-
let mapping_index = insertion_index - 1;
111-
if avma < mappings[mapping_index].end_avma {
112-
mapping_index
113-
} else {
114-
return None;
115-
}
116-
}
117-
};
118-
Some(&mappings[index])
102+
/// Look up the mapping which covers the given address and return
103+
/// the stored value.
104+
pub fn lookup(&self, avma: u64) -> Option<&T> {
105+
self.lookup_impl(avma).map(|m| &m.value)
106+
}
107+
108+
/// Look up the mapping which covers the given address and return
109+
/// its `Mapping<T>``.
110+
fn lookup_impl(&self, avma: u64) -> Option<&Mapping<T>> {
111+
let (_start_avma, last_mapping_starting_at_or_before_avma) =
112+
self.map.range(..=avma).next_back()?;
113+
if avma < last_mapping_starting_at_or_before_avma.end_avma {
114+
Some(last_mapping_starting_at_or_before_avma)
115+
} else {
116+
None
117+
}
119118
}
120119

121120
/// Converts an absolute address (AVMA, actual virtual memory address) into
122121
/// a relative address and the mapping's associated value.
123122
pub fn convert_address(&self, avma: u64) -> Option<(u32, &T)> {
124-
let mapping = match self.lookup(avma) {
123+
let mapping = match self.lookup_impl(avma) {
125124
Some(mapping) => mapping,
126125
None => return None,
127126
};
@@ -138,3 +137,28 @@ struct Mapping<T> {
138137
relative_address_at_start: u32,
139138
value: T,
140139
}
140+
141+
#[cfg(test)]
142+
mod test {
143+
use super::*;
144+
145+
#[test]
146+
fn test_lib_mappings() {
147+
let mut m = LibMappings::new();
148+
m.add_mapping(100, 200, 100, "100..200");
149+
m.add_mapping(200, 250, 200, "200..250");
150+
assert_eq!(m.lookup(200), Some(&"200..250"));
151+
m.add_mapping(180, 220, 180, "180..220");
152+
assert_eq!(m.lookup(200), Some(&"180..220"));
153+
assert_eq!(m.lookup(170), None);
154+
assert_eq!(m.lookup(220), None);
155+
m.add_mapping(225, 250, 225, "225..250");
156+
m.add_mapping(255, 270, 255, "255..270");
157+
m.add_mapping(100, 150, 100, "100..150");
158+
assert_eq!(m.lookup(90), None);
159+
assert_eq!(m.lookup(150), None);
160+
assert_eq!(m.lookup(149), Some(&"100..150"));
161+
assert_eq!(m.lookup(200), Some(&"180..220"));
162+
assert_eq!(m.lookup(260), Some(&"255..270"));
163+
}
164+
}

0 commit comments

Comments
 (0)