FAQ
General
Is PulseMap a HashMap replacement?
No. PulseMap is a bounded cache with automatic eviction. Use it when:
- You need fixed memory usage
- You can tolerate entries being evicted
- You want in-process caching without Redis
Use HashMap when you need to keep every entry forever.
What happens when PulseMap is full?
The least-useful entry in the target bucket is evicted (LFU+LRU hybrid). This is automatic and costs zero additional cache misses. Check eviction_count() to monitor.
Can I turn off eviction?
Not directly, but you can minimize it:
- Use
with_auto_resize(n)— the map doubles when 75% full - Start with a large initial size
- Monitor
eviction_count()— if it’s 0, you’re fine
What’s the maximum key/value size?
- Inline mode: key ≤ 6 bytes, value ≤ 7 bytes (fastest, zero allocation)
- Slab mode: unlimited size (heap allocated)
Both modes are transparent — PulseMap automatically chooses the optimal storage.
Performance
Why is PulseMap faster than HashMap?
Three reasons:
- Cache efficiency: 1 cache line per lookup (vs 2-3 for HashMap)
- No pointer chasing: Inline mode stores data directly in the bucket
- H2 fingerprint: 99.2% of non-matches rejected without key comparison
When is PulseMap slower?
- Iteration — PulseMap doesn’t maintain insertion order
- Very large values — Slab allocation adds overhead
- 99%+ fill rate — Every insert causes an eviction
How does it compare to moka?
Single-thread: moka is significantly slower (161ms vs 6.1ms for 100K inserts). moka uses background maintenance threads and heavy synchronization.
Multi-thread (4T): ShardedPulseMap is 6.5–12x faster than moka across all concurrent workloads.
moka’s strength is its W-TinyLFU eviction policy (better hit rates on skewed workloads). PulseMap wins on raw throughput.
Memory
Does PulseMap leak memory?
No (since v0.6.0). Slab entries are returned to a free list on eviction/removal.
- Rust:
Dropchains through SlabPool + free list - C FFI: User must call
pulse_map_free()(documented)
How much memory does PulseMap use?
Memory = num_buckets × 64 bytes + slab_overhead
For inline-only workloads (small KV pairs): exactly num_buckets × 64 bytes.
Can I use PulseMap in no_std?
Yes! Disable the std feature:
pulse_map = { version = "0.6", default-features = false }
Core data structures (MetaWord, Slot, Bucket) work without allocator.
Concurrency
Is PulseMap thread-safe?
ConcurrentPulseMap— fully thread-safe, single-lock architectureShardedPulseMap— fully thread-safe, 16-shard architecture (recommended for 3+ threads)TypedPulseMapandPulseMapRaw— single-threaded only
Can I set different TTLs for different keys?
Yes! Since v0.6.1, use insert_ttl(key, value, ttl):
#![allow(unused)]
fn main() {
cache.set_ttl(500); // global default
cache.insert_ttl(b"session", b"data", 50); // expires after 50
cache.insert_ttl(b"config", b"val", u32::MAX); // never expires
}
Can I use PulseMap with async/await?
Yes! ConcurrentPulseMap and ShardedPulseMap methods are non-blocking (spinlock, not mutex):
#![allow(unused)]
fn main() {
async fn handler(cache: &ShardedPulseMap<String, String>) {
// Safe to call from async context — won't block the executor
cache.insert("key".to_string(), "val".to_string());
}
}
What happens during resize?
- ConcurrentPulseMap: Stop-the-world (exclusive write lock). ~1ms per 10K entries.
- ShardedPulseMap:
resize_all()rehashes one shard at a time — other shards remain operational.
FFI
Is the C API thread-safe?
Yes! The C API wraps ConcurrentPulseMap internally. You can call pulse_map_insert() from multiple threads simultaneously.