Skip to content

Commit d7e3ac4

Browse files
committed
feat(#108): remove HashMap::upsert*
1 parent a7484bb commit d7e3ac4

File tree

4 files changed

+21
-126
lines changed

4 files changed

+21
-126
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- `ebr::AtomicArc::try_into_arc` -> `ebr::AtomicShared::try_into_shared`.
1414
- `ebr::Ptr::get_arc` -> `ebr::Ptr::get_shared`.
1515
- `*::first_occupied_entry*` -> `*::first_entry*`.
16+
- Remove `HashMap::upsert*`: superseded by `hash_map::Entry::or_insert_with`.
1617
- Remove `HashIndex::update*` and `HashIndex::modify*`: superseded by `HashIndex::entry*`, `HashIndex::get*`, and `hash_index::OccupiedEntry::update`.
1718
- Remove `Hash*::for_each*`: superseded by `HashMap::retain*`.
1819
- `Hash*::clear*`, `Hash*::prune*`, and `Hash*::retain*` return `()`.

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ A collection of high performance containers and utilities for concurrent and asy
3333

3434
## HashMap
3535

36-
[HashMap](#HashMap) is a concurrent hash map that is targeted at highly concurrent write-heavy workloads. [HashMap](#HashMap) is basically an array of entry buckets where each bucket is protected by a special read-write lock providing both blocking and asynchronous methods. The bucket array is fully managed by [EBR](#EBR) enabling lock-free access to it and non-blocking array resizing.
36+
[HashMap](#HashMap) is a concurrent hash map, optimized for highly parallel write-heavy workloads. [HashMap](#HashMap) is structured as a lock-free stack of fixed-size entry bucket arrays. Each bucket is protected by a special read-write lock which provides both blocking and asynchronous methods. The entry bucket array is managed by [EBR](#EBR), thus enabling lock-free access to it and non-blocking container resizing.
3737

3838
### Locking behavior
3939

@@ -43,7 +43,7 @@ Read/write access to an entry is serialized by the read-write lock in the bucket
4343

4444
#### Resize: lock-free
4545

46-
Resizing of the container is totally non-blocking and lock-free; resizing does not block any other read/write access to the container or resizing attempts. _Resizing is analogous to pushing a new bucket array into a lock-free stack_. Each individual entry in the old bucket array will be incrementally relocated to the new bucket array on future access to the container, and the old bucket array gets dropped eventually when it becomes empty.
46+
Resizing of a [HashMap](#HashMap) is completely non-blocking and lock-free; resizing does not block any other read/write access to the container or resizing attempts. _Resizing is analogous to pushing a new bucket array into a lock-free stack_. Each entry in the old bucket array will be incrementally relocated to the new bucket array on future access to the container, and the old bucket array gets dropped eventually after it becomes empty.
4747

4848
### Examples
4949

@@ -66,22 +66,21 @@ let future_insert = hashmap.insert_async(2, 1);
6666
let future_remove = hashmap.remove_async(&1);
6767
```
6868

69-
`upsert` will insert a new entry if the key does not exist, otherwise update the value field.
69+
The `Entry` API of [HashMap](#HashMap) is useful if the workflow is complicated.
7070

7171
```rust
7272
use scc::HashMap;
7373

7474
let hashmap: HashMap<u64, u32> = HashMap::default();
7575

76-
hashmap.upsert(1, || 2, |_, v| *v = 2);
77-
assert_eq!(hashmap.read(&1, |_, v| *v).unwrap(), 2);
78-
hashmap.upsert(1, || 2, |_, v| *v = 3);
79-
assert_eq!(hashmap.read(&1, |_, v| *v).unwrap(), 3);
76+
hashmap.entry(3).or_insert(7);
77+
assert_eq!(hashmap.read(&3, |_, v| *v), Some(7));
8078

81-
let future_upsert = hashmap.upsert_async(2, || 1, |_, v| *v = 3);
79+
let future_entry = hashmap.entry_async(3);
80+
let future_occupied = hashmap.entry_async(4).and_modify(|v| { *v += 1 }).or_insert(5);
8281
```
8382

84-
[HashMap](#HashMap) does not provide an [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html) since it is impossible to confine the lifetime of [Iterator::Item](https://doc.rust-lang.org/std/iter/trait.Iterator.html#associatedtype.Item) to the [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html). The limitation can be circumvented by relying on interior mutability, e.g., let the returned reference hold a lock, however it will easily lead to a deadlock if not correctly used, and frequent acquisition of locks may impact performance. Therefore, [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html) is not implemented, instead, [HashMap](#HashMap) provides a number of methods to iterate over entries synchronously or asynchronously: `any`, `any_async`, `OccupiedEntry::next`, `OccupiedEntry::next_async`, `prune`, `prune_async`, `retain`, `retain_async`, `scan`, and `scan_async`.
83+
[HashMap](#HashMap) does not provide an [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html) since it is impossible to confine the lifetime of [Iterator::Item](https://doc.rust-lang.org/std/iter/trait.Iterator.html#associatedtype.Item) to the [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html). The limitation can be circumvented by relying on interior mutability, e.g., let the returned reference hold a lock, however it will easily lead to a deadlock if not correctly used, and frequent acquisition of locks may impact performance. Therefore, [Iterator](https://doc.rust-lang.org/std/iter/trait.Iterator.html) is not implemented, instead, [HashMap](#HashMap) provides a number of methods to iterate over entries synchronously or asynchronously: `any`, `any_async`, `prune`, `prune_async`, `retain`, `retain_async`, `scan`, `scan_async`, `OccupiedEntry::next`, and `OccupiedEntry::next_async`.
8584

8685
```rust
8786
use scc::HashMap;

src/hash_map.rs

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -519,103 +519,6 @@ where
519519
}
520520
}
521521

522-
/// Constructs the value in-place, or modifies an existing value corresponding to the key.
523-
///
524-
/// # Examples
525-
///
526-
/// ```
527-
/// use scc::HashMap;
528-
///
529-
/// let hashmap: HashMap<u64, u32> = HashMap::default();
530-
///
531-
/// hashmap.upsert(1, || 2, |_, v| *v = 2);
532-
/// assert_eq!(hashmap.read(&1, |_, v| *v).unwrap(), 2);
533-
/// hashmap.upsert(1, || 2, |_, v| *v = 3);
534-
/// assert_eq!(hashmap.read(&1, |_, v| *v).unwrap(), 3);
535-
/// ```
536-
#[inline]
537-
pub fn upsert<C: FnOnce() -> V, U: FnOnce(&K, &mut V)>(
538-
&self,
539-
key: K,
540-
constructor: C,
541-
updater: U,
542-
) {
543-
let guard = Guard::new();
544-
let hash = self.hash(&key);
545-
if let Ok(LockedEntry {
546-
mut locker,
547-
data_block_mut,
548-
mut entry_ptr,
549-
index: _,
550-
}) = self.reserve_entry(&key, hash, &mut (), &guard)
551-
{
552-
if entry_ptr.is_valid() {
553-
let (k, v) = entry_ptr.get_mut(data_block_mut, &mut locker);
554-
updater(k, v);
555-
return;
556-
}
557-
let val = constructor();
558-
locker.insert_with(
559-
data_block_mut,
560-
BucketArray::<K, V, SEQUENTIAL>::partial_hash(hash),
561-
|| (key, val),
562-
&guard,
563-
);
564-
};
565-
}
566-
567-
/// Constructs the value in-place, or modifies an existing value corresponding to the key.
568-
///
569-
/// It is an asynchronous method returning an `impl Future` for the caller to await.
570-
///
571-
/// # Examples
572-
///
573-
/// ```
574-
/// use scc::HashMap;
575-
///
576-
/// let hashmap: HashMap<u64, u32> = HashMap::default();
577-
///
578-
/// let future_upsert = hashmap.upsert_async(1, || 2, |_, v| *v = 3);
579-
/// ```
580-
#[inline]
581-
pub async fn upsert_async<C: FnOnce() -> V, U: FnOnce(&K, &mut V)>(
582-
&self,
583-
key: K,
584-
constructor: C,
585-
updater: U,
586-
) {
587-
let hash = self.hash(&key);
588-
loop {
589-
let mut async_wait = AsyncWait::default();
590-
let mut async_wait_pinned = Pin::new(&mut async_wait);
591-
{
592-
let guard = Guard::new();
593-
if let Ok(LockedEntry {
594-
mut locker,
595-
data_block_mut,
596-
mut entry_ptr,
597-
index: _,
598-
}) = self.reserve_entry(&key, hash, &mut async_wait_pinned, &guard)
599-
{
600-
if entry_ptr.is_valid() {
601-
let (k, v) = entry_ptr.get_mut(data_block_mut, &mut locker);
602-
updater(k, v);
603-
} else {
604-
let val = constructor();
605-
locker.insert_with(
606-
data_block_mut,
607-
BucketArray::<K, V, SEQUENTIAL>::partial_hash(hash),
608-
|| (key, val),
609-
&guard,
610-
);
611-
}
612-
return;
613-
};
614-
}
615-
async_wait_pinned.await;
616-
}
617-
}
618-
619522
/// Removes a key-value pair if the key exists.
620523
///
621524
/// Returns `None` if the key does not exist.

src/tests/correctness.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,9 @@ mod hashmap_test {
219219
}
220220
for id in range.clone() {
221221
if id % 10 == 0 {
222-
hashmap_clone.upsert_async(id, || id, |_, v| *v = id).await;
222+
hashmap_clone.entry_async(id).await.or_insert(id);
223223
} else if id % 5 == 0 {
224-
hashmap_clone.upsert(id, || id, |_, v| *v = id);
224+
hashmap_clone.entry(id).or_insert(id);
225225
} else if id % 2 == 0 {
226226
let result = hashmap_clone.insert_async(id, id).await;
227227
assert!(result.is_ok());
@@ -245,11 +245,7 @@ mod hashmap_test {
245245
}
246246
}
247247
for id in range.clone() {
248-
if id % 7 == 0 {
249-
hashmap_clone
250-
.upsert_async(id, || id, |_, v| *v = id + 1)
251-
.await;
252-
} else if id % 7 == 5 {
248+
if id % 7 == 4 {
253249
let entry = hashmap_clone.entry(id);
254250
match entry {
255251
Entry::Occupied(mut o) => {
@@ -702,12 +698,12 @@ mod hashmap_test {
702698
let hashmap: HashMap<Data, Data> = HashMap::default();
703699
for d in key..(key + range) {
704700
assert!(hashmap.insert(Data::new(d, checker.clone()), Data::new(d, checker.clone())).is_ok());
705-
hashmap.upsert(Data::new(d, checker.clone()), || Data::new(d + 1, checker.clone()), |_, v| *v = Data::new(d + 2, checker.clone()));
701+
*hashmap.entry(Data::new(d, checker.clone())).or_insert(Data::new(d + 1, checker.clone())).get_mut() = Data::new(d + 2, checker.clone());
706702
}
707703

708704
for d in (key + range)..(key + range + range) {
709705
assert!(hashmap.insert(Data::new(d, checker.clone()), Data::new(d, checker.clone())).is_ok());
710-
hashmap.upsert(Data::new(d, checker.clone()), || Data::new(d, checker.clone()), |_, v| *v = Data::new(d + 1, checker.clone()));
706+
*hashmap.entry(Data::new(d, checker.clone())).or_insert(Data::new(d + 1, checker.clone())).get_mut() = Data::new(d + 2, checker.clone());
711707
}
712708

713709
let mut removed = 0;
@@ -734,14 +730,14 @@ mod hashmap_test {
734730

735731
for d in key..(key + range) {
736732
assert!(hashmap.insert(Data::new(d, checker.clone()), Data::new(d, checker.clone())).is_ok());
737-
hashmap.upsert(Data::new(d, checker.clone()), || Data::new(d, checker.clone()), |_, v| *v = Data::new(d + 2, checker.clone()));
733+
*hashmap.entry(Data::new(d, checker.clone())).or_insert(Data::new(d + 1, checker.clone())).get_mut() = Data::new(d + 2, checker.clone());
738734
}
739735
hashmap.clear();
740736
assert_eq!(checker.load(Relaxed), 0);
741737

742738
for d in key..(key + range) {
743739
assert!(hashmap.insert(Data::new(d, checker.clone()), Data::new(d, checker.clone())).is_ok());
744-
hashmap.upsert(Data::new(d, checker.clone()), || Data::new(d, checker.clone()), |_, v| *v = Data::new(d + 2, checker.clone()));
740+
*hashmap.entry(Data::new(d, checker.clone())).or_insert(Data::new(d + 1, checker.clone())).get_mut() = Data::new(d + 2, checker.clone());
745741
}
746742
assert_eq!(checker.load(Relaxed), range * 2);
747743
drop(hashmap);
@@ -2787,15 +2783,11 @@ mod random_failure_test {
27872783
let hashmap: HashMap<usize, R> = HashMap::default();
27882784
for k in 0..workload_size {
27892785
let result: Result<(), Box<dyn Any + Send>> = catch_unwind(|| {
2790-
hashmap.upsert(
2791-
k as usize,
2792-
|| {
2793-
let mut r = R::new(&INST_CNT, &NEVER_PANIC);
2794-
r.2 = true;
2795-
r
2796-
},
2797-
|_, _| (),
2798-
);
2786+
hashmap.entry(k as usize).or_insert_with(|| {
2787+
let mut r = R::new(&INST_CNT, &NEVER_PANIC);
2788+
r.2 = true;
2789+
r
2790+
});
27992791
});
28002792
NEVER_PANIC.store(true, Relaxed);
28012793
assert_eq!(

0 commit comments

Comments
 (0)