pulse_map 0.6.3

A CPU cache-line hash table with zero-cost eviction. Every bucket fits in exactly one 64-byte cache line with embedded LFU+LRU eviction metadata.
Documentation
# 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:
1. Use `with_auto_resize(n)` — the map doubles when 75% full
2. Start with a large initial size
3. 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:
1. **Cache efficiency:** 1 cache line per lookup (vs 2-3 for HashMap)
2. **No pointer chasing:** Inline mode stores data directly in the bucket
3. **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:** `Drop` chains 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:
```toml
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 architecture
- `ShardedPulseMap` — fully thread-safe, 16-shard architecture (recommended for 3+ threads)
- `TypedPulseMap` and `PulseMapRaw` — single-threaded only

### Can I set different TTLs for different keys?

**Yes!** Since v0.6.1, use `insert_ttl(key, value, ttl)`:

```rust
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):

```rust
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.