lazily
Lazy reactive primitives for Rust — Context, Slots, Cells with automatic dependency tracking and cache invalidation.
Overview
lazily provides four core primitives for lazy reactive computation:
- Context — owns all reactive state and manages the dependency graph
- Slot — a lazily-computed cached value that automatically tracks dependencies
- Cell — a mutable value that invalidates dependent Slots when changed
- Effect — a side-effect callback that automatically reruns after tracked dependencies invalidate
Values are lazy: dependents are marked dirty on invalidation but only validated or recomputed when accessed. This contrasts with eager "signal" systems that recompute immediately.
ctx.memo() Slots use a memo guard: if recomputation produces the same value, downstream dirty caches and effects are left alone.
Multiple updates can be grouped with ctx.batch(...) so invalidation and effect reruns happen once after the outermost batch exits.
Usage
use Context;
let ctx = new;
// Create a mutable cell
let counter = ctx.cell;
// Create a derived value (automatically tracks dependencies)
let doubled = ctx.computed;
assert_eq!;
// Mutate the cell — dependents are marked dirty (not recomputed yet)
ctx.set_cell;
// Slot recomputes lazily on next access
assert_eq!;
// Effects run immediately and then after tracked dependencies change
let effect = ctx.effect;
ctx.set_cell; // schedules and runs the effect once
effect.dispose; // unsubscribes and prevents future reruns
// Batch writes coalesce invalidation and effect reruns.
ctx.batch;
Why Lazy?
| Lazy (Slots) | Eager (Signals) | |
|---|---|---|
| When does recomputation happen? | On access (get) |
Immediately on change |
| Wasted work | Zero — only compute what's read | Can compute values nobody uses |
| Glitch-free | By construction | Requires topological sorting |
| Ordering | Irrelevant — pull-based | Critical — push-based DAG walk |
| Use case | Request handling, data pipelines | UI rendering, real-time updates |
In a web server handling requests, you might have 50 computed values available but any given request only uses 5. With eager reactivity, all 50 recompute on every change. With lazy, only the 5 actually accessed compute.
Core Concepts
Context
Context owns all Slots and Cells. It manages the dependency graph and provides the API for creating, reading, and mutating reactive values. Think of it as the "world" for your reactive computations — in web frameworks, this maps to a request context, application scope, or component tree.
The current Context is intentionally single-threaded. It uses RefCell and
non-Send callback storage to keep the fast path allocation-only and mutex-free.
Create independent contexts per OS thread for local graphs, or use
ThreadSafeContext when one reactive graph must be shared across threads.
Slot
A SlotHandle<T> wraps a compute function Fn(&Context) -> T. The result is cached after first access. Dependencies are discovered automatically via a thread-local tracking stack — any Slot or Cell accessed during computation becomes a dependency. ctx.computed() is the ergonomic name for a derived value; ctx.slot() is the same primitive. Use ctx.memo() when T: PartialEq and equal recomputations should suppress downstream work.
When a dependency is invalidated, the Slot marks its cached value dirty. It does not validate or recompute until ctx.get() is called again.
For ctx.memo() slots, if recomputation returns a value equal to the previous cache, downstream dirty Slots become fresh without recomputing, and scheduled effects that only depended on unchanged Slots skip cleanup/rerun.
Dependencies are dynamic. Every time a Slot recomputes, it re-discovers its dependencies from scratch. If your compute function has conditional branches that access different Cells depending on state, the dependency graph updates automatically. No stale subscriptions, no manual cleanup.
Cell
A CellHandle<T> holds a mutable value. ctx.set_cell() compares old and new values via PartialEq — if unchanged, no invalidation occurs. If changed, all dependent Slots are recursively marked dirty.
Batch Updates
ctx.batch(|ctx| { ... }) groups multiple cell updates and explicit slot/cell clears into one invalidation pass. Nested batches flush only when the outermost batch exits. Direct ctx.get_cell() reads inside the callback see the latest cell value immediately; changed-cell dependents are marked dirty after the batch, so Slot reads during the callback return their pre-batch cached value until the batch completes.
Effect
An EffectHandle represents a side-effect callback registered with ctx.effect(). Effects run immediately, track any Slots or Cells read during that run, and rerun after those dependencies invalidate. Scheduled effect reruns are flushed after the invalidation pass, so diamond dependency paths coalesce to one rerun. Effects scheduled only by dirty Slot dependencies first validate those Slots and skip cleanup/rerun when values are unchanged.
Effects can return a cleanup closure. Cleanup runs before the next rerun and when the handle is disposed:
let effect = ctx.effect;
effect.dispose;
API
| Method | Purpose |
|---|---|
Context::new() |
Create a new context |
ctx.computed(|ctx| T) |
Create a derived lazily-computed value |
ctx.slot(|ctx| T) |
Create a lazily-computed slot; synonym of ctx.computed() |
ctx.memo(|ctx| T) |
Create a lazily-computed slot with a PartialEq memoization guard |
ctx.get(&slot) |
Get value (computes if unset) |
ctx.cell(value) |
Create a mutable cell |
ctx.get_cell(&cell) |
Get cell value |
ctx.set_cell(&cell, value) |
Update cell (marks dependents dirty if changed) |
ctx.batch(|ctx| { ... }) |
Defer changed-cell dirty marking and explicit clears until the outermost batch exits |
ctx.effect(|ctx| { ... }) |
Run an effect immediately and rerun it after tracked dependencies invalidate |
ctx.is_set(&slot) |
Check if slot has a cached, fresh value |
slot.clear(&ctx) |
Clear cached value and cascade to dependents |
cell.clear_dependents(&ctx) |
Clear downstream slots without changing cell value |
effect.dispose(&ctx) |
Dispose an effect and unsubscribe dependencies |
effect.is_active(&ctx) |
Check whether an effect is still registered |
ThreadSafeContext
ThreadSafeContext is the mutex-backed counterpart for sharing one reactive
graph across OS threads. It mirrors the core Context methods while requiring
Send + Sync + 'static values and compute/effect callbacks. The graph lock is
released before user compute callbacks, effect callbacks, or cleanup closures
run, so callbacks can re-enter the same context without deadlocking. If a slot
is invalidated while its callback is running, the stale result is discarded and
the getter retries before returning a fresh value.
Design
- Lazy, not eager: Slots mark dirty on invalidation but only validate/recompute on access
- Ergonomic aliases:
ctx.computed()names derived values while preservingctx.slot()for low-level terminology - PartialEq guard:
Cell.set()only invalidates when value actually changes - Memo guard: Dirty
ctx.memo()Slots compare recomputed values and suppress downstream recomputation/effect reruns when values are equal - Dynamic dependencies: Edges re-discovered on each recomputation (no stale subscriptions)
- Batching: Multiple writes share one invalidation/effect flush boundary
- Effect scheduling: Effects rerun after dependency invalidation and coalesce duplicate schedules
- Slot-id-indexed contiguous node storage for the single-threaded fast path
- Interior mutability via
RefCell(single-threaded) - Thread-local tracking stack for automatic dependency discovery
- Zero mandatory runtime dependencies in the default library surface
- Optional
instrumentationfeature for benchmark counters, lock timing, and thread-safe lock attribution
Threading Roadmap
lazily-rs guarantees local, single-threaded Context graphs plus an explicit
ThreadSafeContext for shared graphs. SlotHandle<T> and CellHandle<T> are
Send + Sync when T is Send + Sync, and EffectHandle is also Send + Sync,
but handles must be used with their owning context.
Enable the optional loom feature to run the thread-safe synchronization model:
Enable the optional tokio feature for sync-on-Tokio integration tests and the
tokio_sync example:
The feature proves ThreadSafeContext can be shared through tokio::spawn and
tokio::task::spawn_blocking. It does not add async computations or effects;
those need the separate AsyncContext design captured in SPEC.md, including
in-flight future deduplication, stale completion handling, cleanup ordering, and
separate Send versus LocalSet surfaces.
ThreadSafeContext intentionally keeps a mutex-first graph lock. In-flight
recompute waiters use per-slot sidecar Condvars so they can park while the
compute owner runs user code, and a completion only wakes waiters for that
finished slot. Fresh cached gets clone the cached value under one graph lock
without recursively validating unchanged dependencies. Broader RwLock,
sharded-lock, or CAS variants should wait for lock wait/hold benchmark evidence
and a Loom or Shuttle safety model for stale in-flight completion, invalidation
during compute, effect scheduling/disposal, and re-entrant callbacks. A
lock-free versioned optimistic read path is deferred until cached values can be
retained independently of the mutex-protected erased-value storage.
Benchmark Results
Generated for package lazily version 0.5.0.
Environment: rustc 1.94.0 (4a4ef493e 2026-03-02) on x86_64-unknown-linux-gnu.
Refresh command:
Regression workflow:
# apply the performance patch
Criterion estimates are local mean wall-clock time per iteration.
| Group | Case | Mean | 95% CI |
|---|---|---|---|
| cached_reads | context | 11.539 ns | 11.507 ns - 11.572 ns |
| cached_reads | thread_safe_context | 108.430 ns | 108.128 ns - 108.767 ns |
| cold_first_get | context | 111.137 ns | 107.964 ns - 114.579 ns |
| cold_first_get | thread_safe_context | 1.043 us | 1.037 us - 1.051 us |
| dependency_fan_out | context / 32 | 5.034 us | 4.572 us - 5.701 us |
| dependency_fan_out | context / 256 | 37.165 us | 33.933 us - 41.657 us |
| dependency_fan_out | thread_safe_context / 32 | 37.770 us | 37.279 us - 38.316 us |
| dependency_fan_out | thread_safe_context / 256 | 303.666 us | 298.118 us - 309.302 us |
| set_cell_invalidation | high_fan_out / 512 | 200.697 us | 159.531 us - 247.363 us |
| set_cell_invalidation | same_slot_contention / 1 | 87.070 us | 85.688 us - 88.584 us |
| set_cell_invalidation | same_slot_contention / 2 | 233.063 us | 228.504 us - 237.591 us |
| set_cell_invalidation | same_slot_contention / 4 | 458.064 us | 439.022 us - 475.339 us |
| set_cell_invalidation | same_slot_contention / 8 | 1.043 ms | 994.161 us - 1.095 ms |
| set_cell_invalidation | same_slot_contention / 16 | 2.377 ms | 2.272 ms - 2.489 ms |
| set_cell_invalidation | independent_slot_contention / 1 | 83.257 us | 81.103 us - 85.258 us |
| set_cell_invalidation | independent_slot_contention / 2 | 216.920 us | 213.765 us - 220.476 us |
| set_cell_invalidation | independent_slot_contention / 4 | 436.898 us | 423.757 us - 449.882 us |
| set_cell_invalidation | independent_slot_contention / 8 | 995.519 us | 956.797 us - 1.034 ms |
| set_cell_invalidation | independent_slot_contention / 16 | 2.466 ms | 2.410 ms - 2.520 ms |
| set_cell_invalidation | batched_write_bursts / 1 | 226.884 us | 225.378 us - 228.459 us |
| set_cell_invalidation | batched_write_bursts / 2 | 452.789 us | 425.137 us - 482.300 us |
| set_cell_invalidation | batched_write_bursts / 4 | 962.385 us | 896.799 us - 1.031 ms |
| set_cell_invalidation | batched_write_bursts / 8 | 2.428 ms | 2.289 ms - 2.548 ms |
| set_cell_invalidation | batched_write_bursts / 16 | 7.147 ms | 6.875 ms - 7.354 ms |
| memo_equality_suppression | context | 4.664 us | 4.184 us - 5.344 us |
| memo_equality_suppression | thread_safe_context | 14.395 us | 13.533 us - 15.326 us |
| effect_flushing | context | 87.949 ns | 87.665 ns - 88.220 ns |
| effect_flushing | thread_safe_context | 1.310 us | 1.308 us - 1.312 us |
| batch_storms | context / 64 | 3.540 us | 3.525 us - 3.558 us |
| batch_storms | thread_safe_context / 64 | 34.530 us | 34.501 us - 34.562 us |
| thread_safe_contention | same_slot_write_read / 1 | 212.584 us | 210.972 us - 214.085 us |
| thread_safe_contention | same_slot_write_read / 2 | 691.897 us | 680.689 us - 705.398 us |
| thread_safe_contention | same_slot_write_read / 4 | 2.308 ms | 2.127 ms - 2.499 ms |
| thread_safe_contention | same_slot_write_read / 8 | 8.115 ms | 7.206 ms - 9.078 ms |
| thread_safe_contention | same_slot_write_read / 16 | 39.306 ms | 38.520 ms - 40.085 ms |
| thread_safe_contention | independent_slots / 1 | 216.053 us | 213.457 us - 219.868 us |
| thread_safe_contention | independent_slots / 2 | 644.221 us | 635.228 us - 653.876 us |
| thread_safe_contention | independent_slots / 4 | 1.507 ms | 1.443 ms - 1.580 ms |
| thread_safe_contention | independent_slots / 8 | 3.673 ms | 3.522 ms - 3.821 ms |
| thread_safe_contention | independent_slots / 16 | 11.738 ms | 11.435 ms - 12.008 ms |
| thread_safe_contention | read_mostly_waiters / 1 | 214.971 us | 214.226 us - 215.644 us |
| thread_safe_contention | read_mostly_waiters / 2 | 316.049 us | 313.553 us - 318.465 us |
| thread_safe_contention | read_mostly_waiters / 4 | 513.081 us | 510.400 us - 515.575 us |
| thread_safe_contention | read_mostly_waiters / 8 | 1.093 ms | 1.070 ms - 1.115 ms |
| thread_safe_contention | read_mostly_waiters / 16 | 3.133 ms | 3.065 ms - 3.200 ms |
| thread_safe_contention | batched_write_bursts / 1 | 475.724 us | 472.921 us - 478.362 us |
| thread_safe_contention | batched_write_bursts / 2 | 832.325 us | 793.809 us - 865.497 us |
| thread_safe_contention | batched_write_bursts / 4 | 1.481 ms | 1.418 ms - 1.543 ms |
| thread_safe_contention | batched_write_bursts / 8 | 3.248 ms | 2.894 ms - 3.600 ms |
| thread_safe_contention | batched_write_bursts / 16 | 7.920 ms | 7.798 ms - 8.041 ms |
| profile_instrumentation | context_snapshot | 414.270 ns | 412.262 ns - 416.413 ns |
| profile_instrumentation | thread_safe_snapshot | 302.045 us | 299.812 us - 303.927 us |
Instrumentation snapshots are single local profile runs captured by
examples/instrumentation_profile.rs.
| Profile | Alloc | Recomputes | Duplicate recomputes | Edges + | Edges - | Effect pushes | Max queue | Lock acquisitions | Lock wait | Lock hold |
|---|---|---|---|---|---|---|---|---|---|---|
| context_memo_effect | 4 | 3 | 0 | 4 | 1 | 2 | 1 | 0 | 0.000 ns | 0.000 ns |
| context_fan_out_32 | 33 | 64 | 0 | 64 | 32 | 0 | 0 | 0 | 0.000 ns | 0.000 ns |
| context_batch_storm_64 | 65 | 0 | 0 | 128 | 64 | 2 | 1 | 0 | 0.000 ns | 0.000 ns |
| thread_safe_first_get_2 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 17 | 275.122 us | 22.190 us |
| thread_safe_set_cell_invalidation_high_fan_out_512 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 130.000 ns | 1.086 ms |
| thread_safe_set_cell_invalidation_same_slot_contention_1 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 60 | 2.480 us | 42.291 us |
| thread_safe_set_cell_invalidation_same_slot_contention_2 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 108 | 166.742 us | 166.842 us |
| thread_safe_set_cell_invalidation_same_slot_contention_4 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 164 | 676.795 us | 218.602 us |
| thread_safe_set_cell_invalidation_same_slot_contention_8 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 396 | 1.585 ms | 288.262 us |
| thread_safe_set_cell_invalidation_same_slot_contention_16 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 698 | 10.444 ms | 642.304 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_1 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 57 | 2.150 us | 36.700 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_2 | 4 | 2 | 0 | 2 | 0 | 0 | 0 | 116 | 88.440 us | 73.130 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_4 | 8 | 4 | 0 | 4 | 0 | 0 | 0 | 234 | 317.862 us | 149.802 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_8 | 16 | 8 | 0 | 8 | 0 | 0 | 0 | 469 | 1.507 ms | 298.873 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_16 | 32 | 16 | 0 | 16 | 0 | 0 | 0 | 878 | 7.966 ms | 629.426 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_1 | 5 | 1 | 0 | 4 | 0 | 0 | 0 | 167 | 5.390 us | 144.081 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_2 | 9 | 1 | 0 | 8 | 0 | 0 | 0 | 249 | 206.492 us | 165.862 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_4 | 17 | 1 | 0 | 16 | 0 | 0 | 0 | 464 | 894.186 us | 234.943 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_8 | 33 | 1 | 0 | 32 | 0 | 0 | 0 | 906 | 3.742 ms | 426.585 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_16 | 65 | 1 | 0 | 64 | 0 | 0 | 0 | 1802 | 18.639 ms | 925.557 us |
| thread_safe_contention_same_slot_write_read_1 | 2 | 17 | 0 | 1 | 0 | 0 | 0 | 187 | 7.080 us | 81.030 us |
| thread_safe_contention_same_slot_write_read_2 | 2 | 27 | 0 | 1 | 0 | 0 | 0 | 422 | 393.354 us | 216.702 us |
| thread_safe_contention_same_slot_write_read_4 | 2 | 50 | 0 | 1 | 0 | 0 | 0 | 938 | 2.382 ms | 464.596 us |
| thread_safe_contention_same_slot_write_read_8 | 2 | 99 | 0 | 1 | 0 | 0 | 0 | 2328 | 15.365 ms | 1.157 ms |
| thread_safe_contention_same_slot_write_read_16 | 2 | 198 | 0 | 1 | 0 | 0 | 0 | 6282 | 118.436 ms | 3.598 ms |
| thread_safe_contention_independent_slots_1 | 2 | 16 | 0 | 1 | 0 | 0 | 0 | 178 | 6.181 us | 73.240 us |
| thread_safe_contention_independent_slots_2 | 4 | 33 | 0 | 2 | 0 | 0 | 0 | 365 | 237.003 us | 162.170 us |
| thread_safe_contention_independent_slots_4 | 8 | 67 | 0 | 4 | 0 | 0 | 0 | 738 | 1.514 ms | 344.394 us |
| thread_safe_contention_independent_slots_8 | 16 | 135 | 0 | 8 | 0 | 0 | 0 | 1474 | 8.742 ms | 761.575 us |
| thread_safe_contention_independent_slots_16 | 32 | 271 | 0 | 16 | 0 | 0 | 0 | 2911 | 40.846 ms | 1.595 ms |
| thread_safe_contention_read_mostly_waiters_1 | 2 | 17 | 0 | 1 | 0 | 0 | 0 | 187 | 7.370 us | 80.012 us |
| thread_safe_contention_read_mostly_waiters_2 | 2 | 17 | 0 | 1 | 0 | 0 | 0 | 235 | 115.911 us | 107.851 us |
| thread_safe_contention_read_mostly_waiters_4 | 2 | 19 | 0 | 1 | 0 | 0 | 0 | 315 | 421.133 us | 131.601 us |
| thread_safe_contention_read_mostly_waiters_8 | 2 | 22 | 0 | 1 | 0 | 0 | 0 | 472 | 1.839 ms | 181.500 us |
| thread_safe_contention_read_mostly_waiters_16 | 2 | 23 | 0 | 1 | 0 | 0 | 0 | 889 | 9.366 ms | 349.341 us |
| thread_safe_contention_batched_write_bursts_1 | 5 | 16 | 0 | 4 | 0 | 0 | 0 | 423 | 14.360 us | 236.352 us |
| thread_safe_contention_batched_write_bursts_2 | 9 | 3 | 0 | 8 | 0 | 0 | 0 | 325 | 192.702 us | 151.821 us |
| thread_safe_contention_batched_write_bursts_4 | 17 | 3 | 0 | 16 | 0 | 0 | 0 | 695 | 1.495 ms | 349.063 us |
| thread_safe_contention_batched_write_bursts_8 | 33 | 3 | 0 | 32 | 0 | 0 | 0 | 1237 | 4.578 ms | 545.254 us |
| thread_safe_contention_batched_write_bursts_16 | 65 | 3 | 0 | 64 | 0 | 0 | 0 | 2453 | 21.335 ms | 1.100 ms |
ThreadSafe lock attribution for contention profiles:
| Profile | Site | Lock acquisitions | Lock wait | Lock hold |
|---|---|---|---|---|
| thread_safe_set_cell_invalidation_high_fan_out_512 | other | 2 | 90.000 ns | 180.000 ns |
| thread_safe_set_cell_invalidation_high_fan_out_512 | set_cell_invalidation | 1 | 40.000 ns | 1.086 ms |
| thread_safe_set_cell_invalidation_same_slot_contention_1 | other | 38 | 1.590 us | 2.070 us |
| thread_safe_set_cell_invalidation_same_slot_contention_1 | get_refresh | 3 | 120.000 ns | 600.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_1 | dependency_edge | 1 | 40.000 ns | 820.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_1 | set_cell_invalidation | 16 | 650.000 ns | 38.201 us |
| thread_safe_set_cell_invalidation_same_slot_contention_1 | publish | 2 | 80.000 ns | 600.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_2 | other | 70 | 35.350 us | 4.690 us |
| thread_safe_set_cell_invalidation_same_slot_contention_2 | get_refresh | 3 | 120.000 ns | 580.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_2 | dependency_edge | 1 | 40.000 ns | 740.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_2 | set_cell_invalidation | 32 | 131.152 us | 160.182 us |
| thread_safe_set_cell_invalidation_same_slot_contention_2 | publish | 2 | 80.000 ns | 650.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_4 | other | 94 | 400.024 us | 4.500 us |
| thread_safe_set_cell_invalidation_same_slot_contention_4 | get_refresh | 3 | 90.000 ns | 540.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_4 | dependency_edge | 1 | 40.000 ns | 760.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_4 | set_cell_invalidation | 64 | 276.581 us | 212.202 us |
| thread_safe_set_cell_invalidation_same_slot_contention_4 | publish | 2 | 60.000 ns | 600.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_8 | other | 262 | 573.764 us | 7.170 us |
| thread_safe_set_cell_invalidation_same_slot_contention_8 | get_refresh | 3 | 100.000 ns | 540.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_8 | dependency_edge | 1 | 40.000 ns | 720.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_8 | set_cell_invalidation | 128 | 1.011 ms | 279.232 us |
| thread_safe_set_cell_invalidation_same_slot_contention_8 | publish | 2 | 80.000 ns | 600.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_16 | other | 436 | 6.166 ms | 13.600 us |
| thread_safe_set_cell_invalidation_same_slot_contention_16 | get_refresh | 3 | 110.000 ns | 560.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_16 | dependency_edge | 1 | 40.000 ns | 750.000 ns |
| thread_safe_set_cell_invalidation_same_slot_contention_16 | set_cell_invalidation | 256 | 4.278 ms | 626.684 us |
| thread_safe_set_cell_invalidation_same_slot_contention_16 | publish | 2 | 80.000 ns | 710.000 ns |
| thread_safe_set_cell_invalidation_independent_slot_contention_1 | other | 35 | 1.450 us | 2.340 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_1 | get_refresh | 3 | 90.000 ns | 840.000 ns |
| thread_safe_set_cell_invalidation_independent_slot_contention_1 | dependency_edge | 1 | 30.000 ns | 850.000 ns |
| thread_safe_set_cell_invalidation_independent_slot_contention_1 | set_cell_invalidation | 16 | 530.000 ns | 31.370 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_1 | publish | 2 | 50.000 ns | 1.300 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_2 | other | 72 | 3.060 us | 3.540 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_2 | get_refresh | 6 | 230.000 ns | 1.140 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_2 | dependency_edge | 2 | 80.000 ns | 1.440 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_2 | set_cell_invalidation | 32 | 84.920 us | 65.810 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_2 | publish | 4 | 150.000 ns | 1.200 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_4 | other | 146 | 5.940 us | 7.690 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_4 | get_refresh | 12 | 450.000 ns | 2.281 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_4 | dependency_edge | 4 | 160.000 ns | 3.020 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_4 | set_cell_invalidation | 64 | 310.992 us | 134.211 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_4 | publish | 8 | 320.000 ns | 2.600 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_8 | other | 293 | 416.954 us | 14.311 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_8 | get_refresh | 24 | 690.000 ns | 4.000 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_8 | dependency_edge | 8 | 290.000 ns | 5.870 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_8 | set_cell_invalidation | 128 | 1.089 ms | 270.332 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_8 | publish | 16 | 500.000 ns | 4.360 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_16 | other | 526 | 3.788 ms | 28.240 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_16 | get_refresh | 48 | 1.890 us | 8.930 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_16 | dependency_edge | 16 | 660.000 ns | 12.291 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_16 | set_cell_invalidation | 256 | 4.174 ms | 570.495 us |
| thread_safe_set_cell_invalidation_independent_slot_contention_16 | publish | 32 | 1.250 us | 9.470 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_1 | other | 78 | 2.680 us | 4.290 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_1 | get_refresh | 3 | 100.000 ns | 540.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_1 | dependency_edge | 4 | 150.000 ns | 3.210 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_1 | set_cell_invalidation | 80 | 2.390 us | 135.391 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_1 | publish | 2 | 70.000 ns | 650.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_2 | other | 102 | 69.510 us | 7.390 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_2 | get_refresh | 3 | 110.000 ns | 580.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_2 | dependency_edge | 8 | 320.000 ns | 6.670 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_2 | set_cell_invalidation | 134 | 136.472 us | 150.642 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_2 | publish | 2 | 80.000 ns | 580.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_4 | other | 184 | 204.952 us | 15.160 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_4 | get_refresh | 3 | 110.000 ns | 560.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_4 | dependency_edge | 16 | 640.000 ns | 14.191 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_4 | set_cell_invalidation | 259 | 688.414 us | 204.422 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_4 | publish | 2 | 70.000 ns | 610.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_8 | other | 356 | 705.713 us | 27.300 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_8 | get_refresh | 3 | 110.000 ns | 560.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_8 | dependency_edge | 32 | 1.080 us | 27.830 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_8 | set_cell_invalidation | 513 | 3.035 ms | 370.265 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_8 | publish | 2 | 70.000 ns | 630.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_16 | other | 708 | 5.231 ms | 59.270 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_16 | get_refresh | 3 | 110.000 ns | 600.000 ns |
| thread_safe_set_cell_invalidation_batched_write_bursts_16 | dependency_edge | 64 | 2.270 us | 55.020 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_16 | set_cell_invalidation | 1025 | 13.406 ms | 809.917 us |
| thread_safe_set_cell_invalidation_batched_write_bursts_16 | publish | 2 | 70.000 ns | 750.000 ns |
| thread_safe_contention_same_slot_write_read_1 | other | 53 | 2.110 us | 4.360 us |
| thread_safe_contention_same_slot_write_read_1 | get_refresh | 67 | 2.360 us | 12.570 us |
| thread_safe_contention_same_slot_write_read_1 | dependency_edge | 17 | 660.000 ns | 9.380 us |
| thread_safe_contention_same_slot_write_read_1 | set_cell_invalidation | 16 | 690.000 ns | 34.500 us |
| thread_safe_contention_same_slot_write_read_1 | publish | 34 | 1.260 us | 20.220 us |
| thread_safe_contention_same_slot_write_read_2 | other | 95 | 29.202 us | 8.380 us |
| thread_safe_contention_same_slot_write_read_2 | get_refresh | 152 | 144.431 us | 37.850 us |
| thread_safe_contention_same_slot_write_read_2 | dependency_edge | 27 | 30.080 us | 18.980 us |
| thread_safe_contention_same_slot_write_read_2 | set_cell_invalidation | 32 | 43.020 us | 80.371 us |
| thread_safe_contention_same_slot_write_read_2 | publish | 74 | 16.120 us | 55.140 us |
| thread_safe_contention_same_slot_write_read_2 | in_flight_wait | 42 | 130.501 us | 15.981 us |
| thread_safe_contention_same_slot_write_read_4 | other | 182 | 121.960 us | 18.620 us |
| thread_safe_contention_same_slot_write_read_4 | get_refresh | 322 | 696.806 us | 81.982 us |
| thread_safe_contention_same_slot_write_read_4 | dependency_edge | 50 | 128.140 us | 31.920 us |
| thread_safe_contention_same_slot_write_read_4 | set_cell_invalidation | 64 | 257.521 us | 161.520 us |
| thread_safe_contention_same_slot_write_read_4 | publish | 170 | 249.622 us | 115.142 us |
| thread_safe_contention_same_slot_write_read_4 | in_flight_wait | 150 | 927.558 us | 55.412 us |
| thread_safe_contention_same_slot_write_read_8 | other | 355 | 660.716 us | 34.921 us |
| thread_safe_contention_same_slot_write_read_8 | get_refresh | 737 | 3.226 ms | 184.440 us |
| thread_safe_contention_same_slot_write_read_8 | dependency_edge | 99 | 487.465 us | 61.511 us |
| thread_safe_contention_same_slot_write_read_8 | set_cell_invalidation | 128 | 1.114 ms | 335.842 us |
| thread_safe_contention_same_slot_write_read_8 | publish | 419 | 2.007 ms | 320.042 us |
| thread_safe_contention_same_slot_write_read_8 | in_flight_wait | 590 | 7.870 ms | 220.672 us |
| thread_safe_contention_same_slot_write_read_16 | other | 708 | 3.320 ms | 66.181 us |
| thread_safe_contention_same_slot_write_read_16 | get_refresh | 1681 | 15.085 ms | 455.721 us |
| thread_safe_contention_same_slot_write_read_16 | dependency_edge | 198 | 2.063 ms | 134.062 us |
| thread_safe_contention_same_slot_write_read_16 | set_cell_invalidation | 256 | 4.886 ms | 737.076 us |
| thread_safe_contention_same_slot_write_read_16 | publish | 1030 | 9.759 ms | 1.212 ms |
| thread_safe_contention_same_slot_write_read_16 | in_flight_wait | 2409 | 83.323 ms | 993.317 us |
| thread_safe_contention_independent_slots_1 | other | 50 | 1.950 us | 4.750 us |
| thread_safe_contention_independent_slots_1 | get_refresh | 64 | 2.080 us | 11.220 us |
| thread_safe_contention_independent_slots_1 | dependency_edge | 16 | 520.000 ns | 8.790 us |
| thread_safe_contention_independent_slots_1 | set_cell_invalidation | 16 | 570.000 ns | 31.580 us |
| thread_safe_contention_independent_slots_1 | publish | 32 | 1.061 us | 16.900 us |
| thread_safe_contention_independent_slots_2 | other | 103 | 31.490 us | 9.700 us |
| thread_safe_contention_independent_slots_2 | get_refresh | 131 | 118.372 us | 26.880 us |
| thread_safe_contention_independent_slots_2 | dependency_edge | 33 | 32.340 us | 19.600 us |
| thread_safe_contention_independent_slots_2 | set_cell_invalidation | 32 | 17.671 us | 68.280 us |
| thread_safe_contention_independent_slots_2 | publish | 66 | 37.130 us | 37.710 us |
| thread_safe_contention_independent_slots_4 | other | 208 | 186.131 us | 20.420 us |
| thread_safe_contention_independent_slots_4 | get_refresh | 265 | 853.299 us | 64.140 us |
| thread_safe_contention_independent_slots_4 | dependency_edge | 67 | 294.502 us | 41.761 us |
| thread_safe_contention_independent_slots_4 | set_cell_invalidation | 64 | 116.080 us | 139.823 us |
| thread_safe_contention_independent_slots_4 | publish | 134 | 63.700 us | 78.250 us |
| thread_safe_contention_independent_slots_8 | other | 408 | 1.796 ms | 45.990 us |
| thread_safe_contention_independent_slots_8 | get_refresh | 533 | 3.666 ms | 136.012 us |
| thread_safe_contention_independent_slots_8 | dependency_edge | 135 | 1.380 ms | 86.230 us |
| thread_safe_contention_independent_slots_8 | set_cell_invalidation | 128 | 519.470 us | 300.212 us |
| thread_safe_contention_independent_slots_8 | publish | 270 | 1.379 ms | 193.131 us |
| thread_safe_contention_independent_slots_16 | other | 773 | 9.014 ms | 94.241 us |
| thread_safe_contention_independent_slots_16 | get_refresh | 1069 | 15.007 ms | 262.993 us |
| thread_safe_contention_independent_slots_16 | dependency_edge | 271 | 6.419 ms | 178.341 us |
| thread_safe_contention_independent_slots_16 | set_cell_invalidation | 256 | 3.661 ms | 630.187 us |
| thread_safe_contention_independent_slots_16 | publish | 542 | 6.745 ms | 428.804 us |
| thread_safe_contention_read_mostly_waiters_1 | other | 53 | 2.170 us | 4.791 us |
| thread_safe_contention_read_mostly_waiters_1 | get_refresh | 67 | 2.440 us | 12.401 us |
| thread_safe_contention_read_mostly_waiters_1 | dependency_edge | 17 | 700.000 ns | 9.290 us |
| thread_safe_contention_read_mostly_waiters_1 | set_cell_invalidation | 16 | 750.000 ns | 35.210 us |
| thread_safe_contention_read_mostly_waiters_1 | publish | 34 | 1.310 us | 18.320 us |
| thread_safe_contention_read_mostly_waiters_2 | other | 53 | 5.370 us | 4.680 us |
| thread_safe_contention_read_mostly_waiters_2 | get_refresh | 101 | 70.360 us | 22.920 us |
| thread_safe_contention_read_mostly_waiters_2 | dependency_edge | 17 | 5.910 us | 14.140 us |
| thread_safe_contention_read_mostly_waiters_2 | set_cell_invalidation | 16 | 4.250 us | 38.660 us |
| thread_safe_contention_read_mostly_waiters_2 | publish | 39 | 7.610 us | 23.721 us |
| thread_safe_contention_read_mostly_waiters_2 | in_flight_wait | 9 | 22.411 us | 3.730 us |
| thread_safe_contention_read_mostly_waiters_4 | other | 55 | 19.340 us | 5.510 us |
| thread_safe_contention_read_mostly_waiters_4 | get_refresh | 157 | 251.692 us | 38.190 us |
| thread_safe_contention_read_mostly_waiters_4 | dependency_edge | 19 | 13.140 us | 11.790 us |
| thread_safe_contention_read_mostly_waiters_4 | set_cell_invalidation | 16 | 7.310 us | 37.460 us |
| thread_safe_contention_read_mostly_waiters_4 | publish | 49 | 42.201 us | 31.971 us |
| thread_safe_contention_read_mostly_waiters_4 | in_flight_wait | 19 | 87.450 us | 6.680 us |
| thread_safe_contention_read_mostly_waiters_8 | other | 58 | 63.470 us | 6.650 us |
| thread_safe_contention_read_mostly_waiters_8 | get_refresh | 263 | 964.670 us | 66.120 us |
| thread_safe_contention_read_mostly_waiters_8 | dependency_edge | 22 | 35.670 us | 13.330 us |
| thread_safe_contention_read_mostly_waiters_8 | set_cell_invalidation | 16 | 32.651 us | 37.840 us |
| thread_safe_contention_read_mostly_waiters_8 | publish | 63 | 158.223 us | 40.810 us |
| thread_safe_contention_read_mostly_waiters_8 | in_flight_wait | 50 | 583.893 us | 16.750 us |
| thread_safe_contention_read_mostly_waiters_16 | other | 59 | 172.501 us | 7.370 us |
| thread_safe_contention_read_mostly_waiters_16 | get_refresh | 520 | 3.470 ms | 137.120 us |
| thread_safe_contention_read_mostly_waiters_16 | dependency_edge | 23 | 80.500 us | 14.920 us |
| thread_safe_contention_read_mostly_waiters_16 | set_cell_invalidation | 16 | 7.240 us | 42.261 us |
| thread_safe_contention_read_mostly_waiters_16 | publish | 110 | 633.606 us | 95.460 us |
| thread_safe_contention_read_mostly_waiters_16 | in_flight_wait | 161 | 5.002 ms | 52.210 us |
| thread_safe_contention_batched_write_bursts_1 | other | 138 | 5.090 us | 14.470 us |
| thread_safe_contention_batched_write_bursts_1 | get_refresh | 109 | 3.650 us | 18.680 us |
| thread_safe_contention_batched_write_bursts_1 | dependency_edge | 64 | 2.160 us | 33.921 us |
| thread_safe_contention_batched_write_bursts_1 | set_cell_invalidation | 80 | 2.470 us | 141.181 us |
| thread_safe_contention_batched_write_bursts_1 | publish | 32 | 990.000 ns | 28.100 us |
| thread_safe_contention_batched_write_bursts_2 | other | 110 | 61.900 us | 11.060 us |
| thread_safe_contention_batched_write_bursts_2 | get_refresh | 55 | 12.250 us | 11.330 us |
| thread_safe_contention_batched_write_bursts_2 | dependency_edge | 24 | 890.000 ns | 15.510 us |
| thread_safe_contention_batched_write_bursts_2 | set_cell_invalidation | 130 | 117.452 us | 107.771 us |
| thread_safe_contention_batched_write_bursts_2 | publish | 6 | 210.000 ns | 6.150 us |
| thread_safe_contention_batched_write_bursts_4 | other | 216 | 411.964 us | 22.281 us |
| thread_safe_contention_batched_write_bursts_4 | get_refresh | 157 | 283.161 us | 37.810 us |
| thread_safe_contention_batched_write_bursts_4 | dependency_edge | 48 | 10.080 us | 31.480 us |
| thread_safe_contention_batched_write_bursts_4 | set_cell_invalidation | 259 | 653.204 us | 241.761 us |
| thread_safe_contention_batched_write_bursts_4 | publish | 9 | 26.750 us | 11.111 us |
| thread_safe_contention_batched_write_bursts_4 | in_flight_wait | 6 | 109.493 us | 4.620 us |
| thread_safe_contention_batched_write_bursts_8 | other | 422 | 1.793 ms | 46.321 us |
| thread_safe_contention_batched_write_bursts_8 | get_refresh | 199 | 358.093 us | 43.270 us |
| thread_safe_contention_batched_write_bursts_8 | dependency_edge | 96 | 3.530 us | 64.480 us |
| thread_safe_contention_batched_write_bursts_8 | set_cell_invalidation | 514 | 2.423 ms | 374.782 us |
| thread_safe_contention_batched_write_bursts_8 | publish | 6 | 210.000 ns | 16.401 us |
| thread_safe_contention_batched_write_bursts_16 | other | 838 | 5.624 ms | 88.040 us |
| thread_safe_contention_batched_write_bursts_16 | get_refresh | 391 | 1.902 ms | 87.532 us |
| thread_safe_contention_batched_write_bursts_16 | dependency_edge | 192 | 7.050 us | 123.521 us |
| thread_safe_contention_batched_write_bursts_16 | set_cell_invalidation | 1026 | 13.802 ms | 781.677 us |
| thread_safe_contention_batched_write_bursts_16 | publish | 6 | 230.000 ns | 19.300 us |
Multi-Language
lazily is implemented across three languages with shared semantics:
| lazily-rs | lazily-zig | lazily-py | |
|---|---|---|---|
| Context | Owned Context struct |
Explicit allocator | Plain dict |
| Slot creation | Box<dyn Fn> closures |
comptime function pointers |
Lambdas |
| Cell equality | PartialEq trait |
std.meta.eql |
!= operator |
| Thread safety | Single-threaded Context; explicit ThreadSafeContext |
Mutex by default | GIL |
| Storage | Unified generics | .direct / .indirect |
Object identity |
Related
- lazily-zig — Zig implementation with FFI support
- lazily-py — Python implementation with context-as-dict
- Blog post: Lazily — Reactive Primitives Done Right
License
MIT