Skip to content

Memos

Preview — Ranvier is pre-release; its APIs may change.

Memos are pull-based: a read runs them, nothing else does. Two costs therefore matter: the cache hit, which every read pays, and a recomputation, which only a read after invalidation pays. See Benchmarks for configuration, environment and caveats.

MemoBenchmarks

MethodWhat it measures
CachedReadBaseline. An untracked read of a clean memo: close to a field read.
CachedTrackedReadThe same read, tracked. Nothing is listening, so this adds only the tracking check.
RecomputeInvalidate, then read: one body run plus re-collection of the memo's dependencies. Tagged Sentinel.

Recorded 2026-09-28. Source: Ranvier.Benchmarks.Memos.MemoBenchmarks at commit d87920f.

MethodCategoriesMeanErrorStdDevOp/sRatioRatioSDAllocatedAlloc Ratio
CachedReadMemo0.2169 ns0.0152 ns0.0142 ns4,611,057,105.0baseline-NA
CachedTrackedReadMemo0.4765 ns0.0180 ns0.0168 ns2,098,796,549.22.21x slower0.15x-NA
RecomputeSentinel,Memo15.5039 ns0.2091 ns0.1854 ns64,499,876.371.76x slower4.44x-NA

CachedRead is at the resolution limit (see Reading the results), which is why the ratios in this table carry a large RatioSD.

MemoBenchmarks also runs each case under every ThreadAffinity, set by Affinity (Guarded, Unchecked, Serialised), as SignalBenchmarks does. Recompute carries the check twice: on the write and on the stale read. The table above predates the parameter and shows Guarded only.

The counter scenario chain-affinity covers the same two checks: it writes the head of a four-memo chain and reads the stale tail under each affinity. Figures are retired instructions per operation at commit e13f159, .NET 10 with tiered compilation and PGO off (see Instruction counts):

Affinityinstr/opbytes/opLibrary counters/op
Guarded2,1790MemoRecomputes 4
Unchecked2,1770MemoRecomputes 4
Serialised2,5160MemoRecomputes 4

Serialised adds about 340 instructions per write and read of the chain, about 15 % over Guarded; Unchecked matches Guarded within noise. The timing cases are in the suite for local runs (dotnet run --project bench/Ranvier.Benchmarks -c Release -- --filter "*MemoBenchmarks*") and have no published figure yet. The full report is e13f159.md.

ChainBenchmarks

A signal followed by a chain of Depth memos (1, 4, 16 or 64). This shows how propagation scales with depth.

MethodWhat it measures
WriteThenReadTailOne write at the head and one read at the tail. Every memo in the chain is invalidated and recomputed exactly once. Tagged Sentinel.
ReadTailCleanThe same chain read without an intervening write. Every memo is clean, so only the tail is touched.

Recorded 2026-09-28. Source: Ranvier.Benchmarks.Memos.ChainBenchmarks at commit d87920f.

MethodCategoriesDepthMeanErrorStdDevOp/sAllocated
ReadTailCleanMemo10.5058 ns0.0283 ns0.0278 ns1,977,109,044.1-
ReadTailCleanMemo40.5149 ns0.0291 ns0.0272 ns1,942,057,188.8-
ReadTailCleanMemo160.5114 ns0.0285 ns0.0317 ns1,955,597,348.4-
ReadTailCleanMemo640.5106 ns0.0283 ns0.0303 ns1,958,494,380.5-
WriteThenReadTailSentinel,Memo116.2286 ns0.3274 ns0.4021 ns61,619,470.0-
WriteThenReadTailSentinel,Memo464.5471 ns1.3097 ns2.2591 ns15,492,567.6-
WriteThenReadTailSentinel,Memo16253.4166 ns3.1584 ns2.9544 ns3,946,072.1-
WriteThenReadTailSentinel,Memo64998.7070 ns17.5727 ns16.4375 ns1,001,294.6-

DiamondBenchmarks

Two paths from one source reconverging on one reader. The shape exists to catch an implementation that recomputes the joining node twice per write, or pairs a value from before the write with one from after it.

MethodWhat it measures
WriteThenReadOne write to the source followed by one read of the joining node.

Recorded 2026-09-28. Source: Ranvier.Benchmarks.Memos.DiamondBenchmarks at commit d87920f.

MethodMeanErrorStdDevOp/sAllocated
WriteThenRead45.98 ns0.930 ns1.142 ns21,747,537.4-

The Ratio and Alloc Ratio columns, where present, compare each method with the baseline method of the same class on the same run. They describe the relative cost of two operations inside Ranvier, not a comparison with any other library.

Edit this page