#map #hash-map #syn #read-write #disjoint-set #constant-time

syncmap

syncmap is a fast, concurrent cache library built with a focus on performance and correctness. The motivation to build syncmap comes from the sync.Map in Golang

3 releases

0.1.3 Nov 30, 2022
0.1.2 Nov 29, 2022
0.1.1 Nov 28, 2022

#1088 in Concurrency

MIT license

37KB
671 lines

syncmap

syncmap

syncmap is a fast, concurrent cache library

syncmap is a fast, concurrent cache library built with a focus on performance and correctness.

The motivation to build syncmap comes from the sync.Map in Golang.

Summary

Map is like a Hashmap but is safe for concurrent use by multiple thread without additional locking or coordination. Loads, stores, and deletes run in amortized constant time.

The Map type is specialized. Most code should use a plain Rust HashMap instead, with separate locking or coordination, for better type safety and to make it easier to maintain other invariants along with the map content.

The Map type is optimized for two common use cases: (1) when the entry for a given key is only ever written once but read many times, as in caches that only grow, or (2) when multiple thread read, write, and overwrite entries for disjoint sets of keys. In these two cases, use of a Map may significantly reduce lock contention compared to a Rust HashMap paired with a separate Mutex or RWMutex.

The zero Map is empty and ready for use. A Map must not be copied after first use.

In the terminology of the Go memory model, Map arranges that a write operation “synchronizes before” any read operation that observes the effect of the write, where read and write operations are defined as follows. Load, LoadAndDelete, LoadOrStore are read operations; Delete, LoadAndDelete, and Store are write operations; and LoadOrStore is a write operation when it returns loaded set to false.

Status

syncmap is usable but still under active development. We expect it to be production ready in the near future.

Usage

Example

// concurrent exmaple
 use syncmap::map::Map;
fn main() {
 let map = Arc::new(Map::<usize, usize>::new());

 let map1 = map.clone();
 let t1 = std::thread::spawn(move || {
  for i in 0..5000 {
   map1.insert(i, 0, &map1.guard());
  }
 });
 let map2 = map.clone();
 let t2 = std::thread::spawn(move || {
  for i in 0..5000 {
   map2.insert(i, 1, &map2.guard());
  }
 });

 t1.join().unwrap();
 t2.join().unwrap();

 thread::sleep(Duration::from_micros(1000));
 let mut missed = 0;
 let guard = map.guard();
 for i in 0..5000 {
  let v = map.get(&i, &guard);
  if v.is_some() {
   assert!(v == Some(&0) || v == Some(&1));
  } else {
   missed += 1;
  }

  // let kv = map.get_key_value(&i, &guard).unwrap();
  // assert!(kv == (&i, &0) || kv == (&i, &1));
 }
 println!("missed {}", missed)
}
 use syncmap::map::Map;
fn main() {
  let cache = Map::new();

  let guard = cache.guard();
  cache.insert("key", "value1",  &guard);
 
  thread::sleep(Duration::from_millis(50));

  assert_eq!(cache.get(&"key", &guard), Some("value1"));

}

Dependencies

~5–11MB
~107K SLoC