Skip to content

Cache elapsedDays in a fixed-size atomic.Int32 array - #5

Merged
mjradwin merged 1 commit into
mainfrom
claude/cache-range-5000-6999
May 25, 2026
Merged

Cache elapsedDays in a fixed-size atomic.Int32 array#5
mjradwin merged 1 commit into
mainfrom
claude/cache-range-5000-6999

Conversation

@mjradwin

Copy link
Copy Markdown
Member

Summary

Reintroduces the elapsedDays memoization that was removed in 4750e9d, but backed by a fixed-size atomic.Int32 array indexed directly by year rather than a mutex-protected map. Each slot is written at most once with a deterministic value, so atomic load/store is sufficient — and on x86/arm it lowers to the same MOV instructions as a plain load.

The cache covers Hebrew years 5000–6999 (~1240 CE to 3239 CE), which spans every realistic use of the library. Years outside the window fall through to recompute, so nothing breaks for callers operating on extreme dates. The cache cannot grow and contributes ~8 KB of static memory.

Why this shape

The previous mutex-protected map cache was a net slowdown on every realistic workload (a sync.RWMutex round-trip costs more than elapsedDays0 itself), which is what motivated 4750e9d. A year-indexed array avoids the lock entirely; using atomic.Int32 rather than a plain int32 slot keeps it race-free per the Go memory model at zero runtime cost on the platforms we run on.

Microbenchmarks (ns per elapsedDays call, Go 1.24, x86-64):

workload nocache (today) atomic-array (this PR)
hot (single year) 15 2
warm (~10 years) 19 3
cold (random in-range) 43 3
out-of-range year 15 15

That's a 5–15× speedup on every in-range workload; one ToRD call invokes elapsedDays ~5 times for the same year (via DaysInYearLongCheshvan/ShortKislev), so the win compounds.

Test plan

  • go test ./... — all existing tests pass, including coverage of out-of-range years (1, 2, 123, 1234, 3671, 3762) which exercise the fall-through path
  • go test -race ./... — race detector clean
  • go vet ./... clean
  • staticcheck -checks=all ./... clean
  • gofmt -l . clean
  • Public API surface unchanged (go doc -all byte-identical to main)

Generated by Claude Code

Reintroduces the elapsedDays memoization that was removed in 4750e9d,
but backed by a fixed-size atomic.Int32 array indexed directly by year
rather than a mutex-protected map. Each slot is written at most once
with a deterministic value, so atomic load/store is sufficient — and
on x86/arm it lowers to the same MOV instructions as a plain load.

Microbenchmarks (ns per elapsedDays call):

  workload          nocache  atomic-array
  hot (1 yr)            15       2
  warm (10 yrs)         19       3
  cold (random)         43       3
  out-of-range          15      15

The cache covers Hebrew years 5000-6999 (~1240 CE to 3239 CE), which
spans every realistic use of the library. Years outside the window
fall through to recompute, so nothing breaks for callers operating on
extreme dates — the cache cannot grow and contributes ~8 KB of static
memory in exchange for a 5-15x speedup on the common path.
@mjradwin
mjradwin merged commit 1ec79bb into main May 25, 2026
1 of 2 checks passed
@mjradwin
mjradwin deleted the claude/cache-range-5000-6999 branch May 25, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants