Skip to content

Commit b5dafb4

Browse files
authored
Merge pull request #159 from cuviper/safer-raw-table
Reduce unsafe code with hashbrown 0.9.1
2 parents 7704ad9 + de27e9e commit b5dafb4

File tree

3 files changed

+219
-222
lines changed

3 files changed

+219
-222
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ serde = { version = "1.0", optional = true, default-features = false }
3737
rayon = { version = "1.0", optional = true }
3838

3939
[dependencies.hashbrown]
40-
version = "0.9.0"
40+
version = "0.9.1"
4141
default-features = false
4242
features = ["raw"]
4343

src/map/core.rs

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,27 @@ fn get_hash<K, V>(entries: &[Bucket<K, V>]) -> impl Fn(&usize) -> u64 + '_ {
3434
move |&i| entries[i].hash.get()
3535
}
3636

37+
#[inline]
38+
fn equivalent<'a, K, V, Q: ?Sized + Equivalent<K>>(
39+
key: &'a Q,
40+
entries: &'a [Bucket<K, V>],
41+
) -> impl Fn(&usize) -> bool + 'a {
42+
move |&i| Q::equivalent(key, &entries[i].key)
43+
}
44+
45+
#[inline]
46+
fn erase_index(table: &mut RawTable<usize>, hash: HashValue, index: usize) {
47+
table.erase_entry(hash.get(), move |&i| i == index);
48+
}
49+
50+
#[inline]
51+
fn update_index(table: &mut RawTable<usize>, hash: HashValue, old: usize, new: usize) {
52+
let index = table
53+
.get_mut(hash.get(), move |&i| i == old)
54+
.expect("index not found");
55+
*index = new;
56+
}
57+
3758
impl<K, V> Clone for IndexMapCore<K, V>
3859
where
3960
K: Clone,
@@ -160,7 +181,7 @@ impl<K, V> IndexMapCore<K, V> {
160181
pub(crate) fn pop(&mut self) -> Option<(K, V)> {
161182
if let Some(entry) = self.entries.pop() {
162183
let last = self.entries.len();
163-
self.erase_index(entry.hash, last);
184+
erase_index(&mut self.indices, entry.hash, last);
164185
Some((entry.key, entry.value))
165186
} else {
166187
None
@@ -181,6 +202,15 @@ impl<K, V> IndexMapCore<K, V> {
181202
i
182203
}
183204

205+
/// Return the index in `entries` where an equivalent key can be found
206+
pub(crate) fn get_index_of<Q>(&self, hash: HashValue, key: &Q) -> Option<usize>
207+
where
208+
Q: ?Sized + Equivalent<K>,
209+
{
210+
let eq = equivalent(key, &self.entries);
211+
self.indices.get(hash.get(), eq).copied()
212+
}
213+
184214
pub(crate) fn insert_full(&mut self, hash: HashValue, key: K, value: V) -> (usize, Option<V>)
185215
where
186216
K: Eq,
@@ -191,6 +221,154 @@ impl<K, V> IndexMapCore<K, V> {
191221
}
192222
}
193223

224+
/// Remove an entry by shifting all entries that follow it
225+
pub(crate) fn shift_remove_full<Q>(&mut self, hash: HashValue, key: &Q) -> Option<(usize, K, V)>
226+
where
227+
Q: ?Sized + Equivalent<K>,
228+
{
229+
let eq = equivalent(key, &self.entries);
230+
match self.indices.remove_entry(hash.get(), eq) {
231+
Some(index) => {
232+
let (key, value) = self.shift_remove_finish(index);
233+
Some((index, key, value))
234+
}
235+
None => None,
236+
}
237+
}
238+
239+
/// Remove an entry by shifting all entries that follow it
240+
pub(crate) fn shift_remove_index(&mut self, index: usize) -> Option<(K, V)> {
241+
match self.entries.get(index) {
242+
Some(entry) => {
243+
erase_index(&mut self.indices, entry.hash, index);
244+
Some(self.shift_remove_finish(index))
245+
}
246+
None => None,
247+
}
248+
}
249+
250+
/// Remove an entry by shifting all entries that follow it
251+
///
252+
/// The index should already be removed from `self.indices`.
253+
fn shift_remove_finish(&mut self, index: usize) -> (K, V) {
254+
// use Vec::remove, but then we need to update the indices that point
255+
// to all of the other entries that have to move
256+
let entry = self.entries.remove(index);
257+
258+
// correct indices that point to the entries that followed the removed entry.
259+
// use a heuristic between a full sweep vs. a `find()` for every shifted item.
260+
let raw_capacity = self.indices.buckets();
261+
let shifted_entries = &self.entries[index..];
262+
if shifted_entries.len() > raw_capacity / 2 {
263+
// shift all indices greater than `index`
264+
for i in self.indices_mut() {
265+
if *i > index {
266+
*i -= 1;
267+
}
268+
}
269+
} else {
270+
// find each following entry to shift its index
271+
for (i, entry) in (index + 1..).zip(shifted_entries) {
272+
update_index(&mut self.indices, entry.hash, i, i - 1);
273+
}
274+
}
275+
276+
(entry.key, entry.value)
277+
}
278+
279+
/// Remove an entry by swapping it with the last
280+
pub(crate) fn swap_remove_full<Q>(&mut self, hash: HashValue, key: &Q) -> Option<(usize, K, V)>
281+
where
282+
Q: ?Sized + Equivalent<K>,
283+
{
284+
let eq = equivalent(key, &self.entries);
285+
match self.indices.remove_entry(hash.get(), eq) {
286+
Some(index) => {
287+
let (key, value) = self.swap_remove_finish(index);
288+
Some((index, key, value))
289+
}
290+
None => None,
291+
}
292+
}
293+
294+
/// Remove an entry by swapping it with the last
295+
pub(crate) fn swap_remove_index(&mut self, index: usize) -> Option<(K, V)> {
296+
match self.entries.get(index) {
297+
Some(entry) => {
298+
erase_index(&mut self.indices, entry.hash, index);
299+
Some(self.swap_remove_finish(index))
300+
}
301+
None => None,
302+
}
303+
}
304+
305+
/// Finish removing an entry by swapping it with the last
306+
///
307+
/// The index should already be removed from `self.indices`.
308+
fn swap_remove_finish(&mut self, index: usize) -> (K, V) {
309+
// use swap_remove, but then we need to update the index that points
310+
// to the other entry that has to move
311+
let entry = self.entries.swap_remove(index);
312+
313+
// correct index that points to the entry that had to swap places
314+
if let Some(entry) = self.entries.get(index) {
315+
// was not last element
316+
// examine new element in `index` and find it in indices
317+
let last = self.entries.len();
318+
update_index(&mut self.indices, entry.hash, last, index);
319+
}
320+
321+
(entry.key, entry.value)
322+
}
323+
324+
/// Erase `start..end` from `indices`, and shift `end..` indices down to `start..`
325+
///
326+
/// All of these items should still be at their original location in `entries`.
327+
/// This is used by `drain`, which will let `Vec::drain` do the work on `entries`.
328+
fn erase_indices(&mut self, start: usize, end: usize) {
329+
let (init, shifted_entries) = self.entries.split_at(end);
330+
let (start_entries, erased_entries) = init.split_at(start);
331+
332+
let erased = erased_entries.len();
333+
let shifted = shifted_entries.len();
334+
let half_capacity = self.indices.buckets() / 2;
335+
336+
// Use a heuristic between different strategies
337+
if erased == 0 {
338+
// Degenerate case, nothing to do
339+
} else if start + shifted < half_capacity && start < erased {
340+
// Reinsert everything, as there are few kept indices
341+
self.indices.clear();
342+
343+
// Reinsert stable indices
344+
for (i, entry) in enumerate(start_entries) {
345+
self.indices.insert_no_grow(entry.hash.get(), i);
346+
}
347+
348+
// Reinsert shifted indices
349+
for (i, entry) in (start..).zip(shifted_entries) {
350+
self.indices.insert_no_grow(entry.hash.get(), i);
351+
}
352+
} else if erased + shifted < half_capacity {
353+
// Find each affected index, as there are few to adjust
354+
355+
// Find erased indices
356+
for (i, entry) in (start..).zip(erased_entries) {
357+
erase_index(&mut self.indices, entry.hash, i);
358+
}
359+
360+
// Find shifted indices
361+
for ((new, old), entry) in (start..).zip(end..).zip(shifted_entries) {
362+
update_index(&mut self.indices, entry.hash, old, new);
363+
}
364+
} else {
365+
// Sweep the whole table for adjustments
366+
self.erase_indices_sweep(start, end);
367+
}
368+
369+
debug_assert_eq!(self.indices.len(), start + shifted);
370+
}
371+
194372
pub(crate) fn retain_in_order<F>(&mut self, mut keep: F)
195373
where
196374
F: FnMut(&mut K, &mut V) -> bool,
@@ -225,6 +403,17 @@ impl<K, V> IndexMapCore<K, V> {
225403
self.indices.insert_no_grow(entry.hash.get(), i);
226404
}
227405
}
406+
407+
pub(crate) fn reverse(&mut self) {
408+
self.entries.reverse();
409+
410+
// No need to save hash indices, can easily calculate what they should
411+
// be, given that this is an in-place reversal.
412+
let len = self.entries.len();
413+
for i in self.indices_mut() {
414+
*i = len - *i - 1;
415+
}
416+
}
228417
}
229418

230419
/// Entry for an existing key-value pair or a vacant location to

0 commit comments

Comments
 (0)