| English | 简体中文 |
Starshard: a high-performance, lazily sharded concurrent HashMap for Rust.
Sync + Async + Optional Rayon + Optional Serde + Lifecycle + Advanced Features
Production-ready (v1.0+). API stability prioritized.
You often need a fast concurrent map:
RwLock<HashMap<..>> becomes contended under mixed read/write load.Starshard focuses on:
rayon).| Feature | Description | Notes |
|---|---|---|
async |
Adds AsyncShardedHashMap (Tokio RwLock) |
Independent of rayon |
rayon |
Parallel snapshot flatten for large iteration | Used internally; API unchanged |
serde |
Serialize/Deserialize (sync) + async snapshot helper | Hasher not persisted |
lifecycle |
TTL, Eviction, Metrics, Advanced Iteration | Consolidated v0.9 features |
advanced |
Transactions, CAS, Replication, Diagnostics | Consolidated v1.0 features |
| (none) | Pure sync core + Batch Ops | Lowest dependency surface |
Enable all in docs.rs via:
[package.metadata.docs.rs]
all-features = true
[dependencies]
starshard = { version = "1.0", features = ["async", "rayon", "serde", "lifecycle", "advanced"] }
# or minimal:
# starshard = "1.0"
serde_json (tests / examples):
[dev-dependencies]
serde_json = "1"
use starshard::ShardedHashMap;
use rustc_hash::FxBuildHasher;
let map: ShardedHashMap<String, i32, FxBuildHasher> = ShardedHashMap::new(64);
map.insert("a".into(), 1);
assert_eq!(map.get(&"a".into()), Some(1));
assert_eq!(map.len(), 1);
use starshard::ShardedHashMap;
use std::collections::hash_map::RandomState;
let secure = ShardedHashMap::<String, u64, RandomState>
::with_shards_and_hasher(128, RandomState::default ());
secure.insert("k".into(), 7);
#[cfg(feature = "async")]
#[tokio::main]
async fn main() {
use starshard::AsyncShardedHashMap;
let m: AsyncShardedHashMap<String, u32> = AsyncShardedHashMap::new(64);
m.insert("x".into(), 42).await;
assert_eq!(m.get(&"x".into()).await, Some(42));
}
rayon)#[cfg(feature = "rayon")]
{
use starshard::ShardedHashMap;
let m: ShardedHashMap<String, u32> = ShardedHashMap::new(32);
for i in 0..50_000 {
m.insert(format ! ("k{i}"), i);
}
let count = m.iter().count(); // internal parallel flatten
assert_eq!(count, 50_000);
}
Sync:
{ "shard_count": usize, "entries": [[K,V], ...] }.S::default().K: Eq + Hash + Clone + Serialize + Deserialize, V: Clone + Serialize + Deserialize,
S: BuildHasher + Default + Clone.Async:
Serialize; call:#[cfg(all(feature = "async", feature = "serde"))]
{
let snap = async_map.async_snapshot_serializable().await;
let json = serde_json::to_string( & snap).unwrap();
}
len() is maintained atomically (structural insert/remove only).| Scenario | Observation (relative) |
|---|---|
Read-heavy mixed workload vs global RwLock<HashMap> |
Reduced contention |
Large snapshot iteration with rayon (100k+) |
3-4x speedup flattening |
| Sparse shard usage | Only touched shards allocate |
| Batch Insert/Remove | Single lock per shard group |
Do benchmark with your own key/value distribution and CPU topology.
RwLock; iteration snapshots avoid long-lived global blocking.use starshard::ShardedHashMap;
let map: ShardedHashMap<String, i32> = ShardedHashMap::new(64);
// Update only if key exists; single shard lock
map.compute_if_present( & "counter".into(), | v| Some(v + 1));
// Insert only if key absent; single shard lock
let val = map.compute_if_absent("new_key".into(), | | 42);
// Conditional deletion
map.compute_if_present( & "key".into(), | _v| None);
// Batch insert (amortizes shard lock acquisition - single lock per shard)
let entries = vec![("a".into(), 1), ("b".into(), 2), ("c".into(), 3)];
let inserted = map.batch_insert(entries);
// Batch remove
let removed = map.batch_remove(vec!["a".into(), "b".into()]);
// Batch get
let keys = vec!["a", "b", "c"];
let results = map.batch_get( & keys);
// Iterate all keys
let all_keys: Vec<_ > = map.keys().collect();
// Iterate all values
let all_values: Vec<_ > = map.values().collect();
// Standard iteration
for (k, v) in map.iter() {
println ! ("{}: {}", k, v);
}
// Retention filtering
map.retain( | k, v| v > & 10); // Keep only entries where v > 10
// Get shard distribution statistics
let stats = map.shard_stats();
println!("Total slots: {}", stats.total);
println!("Initialized shards: {}", stats.initialized);
println!("Avg load: {:.2}", stats.avg_load);
// Get utilization percentage
let util = map.shard_utilization();
println!("Utilization: {:.1}%", util);
Enable via features = ["lifecycle"].
Enable via features = ["advanced"].
| Feature | v0.7 | v0.8 | v0.9 | v1.0 | Status |
|---|---|---|---|---|---|
| Sharded HashMap (sync) | ✅ | ✅ | ✅ | ✅ | Stable |
| Async (Tokio) | ✅ | ✅ | ✅ | ✅ | Stable |
| Parallel iteration (rayon) | ✅ | ✅ | ✅ | ✅ | Stable |
| Serde (de)serialization | ✅ | ✅ | ✅ | ✅ | Stable |
| Conditional Operations | - | ✅ | ✅ | ✅ | Stable |
| Batch operations | - | ✅ | ✅ | ✅ | Stable |
| TTL/Eviction | - | - | ✅ | ✅ | Stable (lifecycle) |
| Metrics | - | - | ✅ | ✅ | Stable (lifecycle) |
| Transactions | - | - | - | ✅ | Stable (advanced) |
| CAS operations | - | - | - | ✅ | Stable (advanced) |
| Replication | - | - | - | ✅ | Stable (advanced) |
Arc -> RwLock<Vec<Option<Arc<RwLock<HashMap<K,V,S>>>>>> + AtomicUsize(len)
Lazy fill of inner Option slot when first key hashes into shard.
| Goal | Snippet |
|---|---|
| Basic sync | see Quick Start |
| Async insert/get | see Async Usage |
| Parallel iterate | enable rayon |
| Serde snapshot (sync) | serde_json::to_string(&map) |
| Async serde snapshot | async_snapshot_serializable() |
| Custom hasher | with_shards_and_hasher(..) |
Dual license: MIT OR Apache-2.0 (choose either).
PRs welcome: focus on correctness (tests), simplicity, and documentation clarity.
Run:
cargo clippy --all-features -- -D warnings
cargo test --all-features
use starshard::{ShardedHashMap, AsyncShardedHashMap};
#[cfg(feature = "async")]
#[tokio::main]
async fn main() {
let sync_map: ShardedHashMap<u64, u64> = ShardedHashMap::new(32);
sync_map.insert(1, 10);
#[cfg(feature = "serde")]
{
let json = serde_json::to_string(&sync_map).unwrap();
let _de: ShardedHashMap<u64, u64> = serde_json::from_str(&json).unwrap();
}
let async_map: AsyncShardedHashMap<u64, u64> = AsyncShardedHashMap::new(32);
async_map.insert(2, 20).await;
}
Benchmarks and behavior notes are indicative only; validate under production load patterns.