Skip to content

Commit 4fa0e2c

Browse files
committed
Auto merge of #293 - stepancheg:insert-unique-unchecked, r=Amanieu
insert_unique_unchecked operation Sometimes a map is constructed when it is known that all keys are unique (e. e. if keys are coming from another map or from a sorted/deduplicated iterator). In this case we can make insertion faster by skipping a check that a key already exists in the map. `insert_unique_unchecked` is guaranteed to be memory-safe, but does not guarantee anything beyond that: if inserted key is not unique, `HashMap` can panic, loop forever, return incorrect entry etc. Added simple benchmark. `insert_unique_unchecked` is about 30% faster than `insert`. Your mileage may vary of course. Similar PR was [added to `indexmap` crate](indexmap-rs/indexmap#200) and they asked to discuss the name of the operation with `hashbrown` crate owners to come to the same naming convention (if `hashbrown` is willing to have the same operation).
2 parents 728f9e8 + 7002f9f commit 4fa0e2c

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

benches/insert_unique_unchecked.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//! Compare `insert` and `insert_unique_unchecked` operations performance.
2+
3+
#![feature(test)]
4+
5+
extern crate test;
6+
7+
use hashbrown::HashMap;
8+
use test::Bencher;
9+
10+
#[bench]
11+
fn insert(b: &mut Bencher) {
12+
let keys: Vec<String> = (0..1000).map(|i| format!("xxxx{}yyyy", i)).collect();
13+
b.iter(|| {
14+
let mut m = HashMap::with_capacity(1000);
15+
for k in &keys {
16+
m.insert(k, k);
17+
}
18+
m
19+
});
20+
}
21+
22+
#[bench]
23+
fn insert_unique_unchecked(b: &mut Bencher) {
24+
let keys: Vec<String> = (0..1000).map(|i| format!("xxxx{}yyyy", i)).collect();
25+
b.iter(|| {
26+
let mut m = HashMap::with_capacity(1000);
27+
for k in &keys {
28+
m.insert_unique_unchecked(k, k);
29+
}
30+
m
31+
});
32+
}

src/map.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,36 @@ where
12781278
}
12791279
}
12801280

1281+
/// Insert a key-value pair into the map without checking
1282+
/// if the key already exists in the map.
1283+
///
1284+
/// Returns a reference to the key and value just inserted.
1285+
///
1286+
/// This operation is safe if a key does not exist in the map.
1287+
///
1288+
/// However, if a key exists in the map already, the behavior is unspecified:
1289+
/// this operation may panic, loop forever, or any following operation with the map
1290+
/// may panic, loop forever or return arbitrary result.
1291+
///
1292+
/// That said, this operation (and following operations) are guaranteed to
1293+
/// not violate memory safety.
1294+
///
1295+
/// This operation is faster than regular insert, because it does not perform
1296+
/// lookup before insertion.
1297+
///
1298+
/// This operation is useful during initial population of the map.
1299+
/// For example, when constructing a map from another map, we know
1300+
/// that keys are unique.
1301+
#[cfg_attr(feature = "inline-more", inline)]
1302+
pub fn insert_unique_unchecked(&mut self, k: K, v: V) -> (&K, &mut V) {
1303+
let hash = make_insert_hash::<K, S>(&self.hash_builder, &k);
1304+
let bucket = self
1305+
.table
1306+
.insert(hash, (k, v), make_hasher::<K, _, V, S>(&self.hash_builder));
1307+
let (k_ref, v_ref) = unsafe { bucket.as_mut() };
1308+
(k_ref, v_ref)
1309+
}
1310+
12811311
/// Tries to insert a key-value pair into the map, and returns
12821312
/// a mutable reference to the value in the entry.
12831313
///
@@ -3898,6 +3928,18 @@ mod test_map {
38983928
assert_eq!(*m.get(&5).unwrap(), 3);
38993929
}
39003930

3931+
#[test]
3932+
fn test_insert_unique_unchecked() {
3933+
let mut map = HashMap::new();
3934+
let (k1, v1) = map.insert_unique_unchecked(10, 11);
3935+
assert_eq!((&10, &mut 11), (k1, v1));
3936+
let (k2, v2) = map.insert_unique_unchecked(20, 21);
3937+
assert_eq!((&20, &mut 21), (k2, v2));
3938+
assert_eq!(Some(&11), map.get(&10));
3939+
assert_eq!(Some(&21), map.get(&20));
3940+
assert_eq!(None, map.get(&30));
3941+
}
3942+
39013943
#[test]
39023944
fn test_is_empty() {
39033945
let mut m = HashMap::with_capacity(4);

src/set.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,30 @@ where
991991
self.map.insert(value, ()).is_none()
992992
}
993993

994+
/// Insert a value the set without checking if the value already exists in the set.
995+
///
996+
/// Returns a reference to the value just inserted.
997+
///
998+
/// This operation is safe if a value does not exist in the set.
999+
///
1000+
/// However, if a value exists in the set already, the behavior is unspecified:
1001+
/// this operation may panic, loop forever, or any following operation with the set
1002+
/// may panic, loop forever or return arbitrary result.
1003+
///
1004+
/// That said, this operation (and following operations) are guaranteed to
1005+
/// not violate memory safety.
1006+
///
1007+
/// This operation is faster than regular insert, because it does not perform
1008+
/// lookup before insertion.
1009+
///
1010+
/// This operation is useful during initial population of the set.
1011+
/// For example, when constructing a set from another set, we know
1012+
/// that values are unique.
1013+
#[cfg_attr(feature = "inline-more", inline)]
1014+
pub fn insert_unique_unchecked(&mut self, value: T) -> &T {
1015+
self.map.insert_unique_unchecked(value, ()).0
1016+
}
1017+
9941018
/// Adds a value to the set, replacing the existing value, if any, that is equal to the given
9951019
/// one. Returns the replaced value.
9961020
///

0 commit comments

Comments
 (0)