SIGN IN SIGN UP

storage: filesystem, MRU pack ordering

Go's map iteration is randomised, so `findObjectInPackfile`
pays an expected N/2 `MayContain` + `FindOffset` probes on
repos with N packs before finding the right one. Real
repositories routinely reach tens of packs between GC runs,
making this O(N) cost visible on read-heavy workloads.

Add a deterministic iteration order on top of `s.index`: a
parallel `packs []packEntry` slice (hash + idx pair,
mirrored in lockstep with the map under `muI.Lock`, always
reassigned rather than mutated in place). A reader can
snapshot the slice header under `RLock` and release the lock
before iterating, so slow `LazyIndex` I/O on a single pack
does not block a concurrent `Reindex` waiting on `muI.Lock`.

A single-slot MRU hint records the slice position that
served the most recent successful probe. Encode as
`atomic.Int32` of `position + 1` so zero means "no hint"
without a separate nullability flag; storing a small int
avoids the per-call heap allocation that a `*plumbing.Hash`
pointer would incur. The hint is advisory: a stale value
costs one extra `MayContain` probe and falls through to a
full scan, never misroutes. The hint store is conditional on
a real change, so the common stay-on-the-same-pack pattern
hits the MRU fast path with zero atomic writes.

`Reindex` resets the hint when it nils the index, since the
slice position is meaningless against a fresh map.
`PackfileWriter.Notify` appends to `packs` via copy-on-grow
so readers holding the old slice header keep seeing a stable
backing array.

`findObjectInPackfile` returns the `idxfile.Index` directly
so callers in the read path avoid a second map lookup
against `s.index`. `getFromPackfile` is split: the public
entry point keeps the membership probe; a private
`getFromPackfileAt` fetches at a pre-located pack/offset for
callers that already ran the probe.

`BenchmarkFindObjectInPackfile_MRU_Hit` (Apple M4 Pro,
32-pack synthetic fixture, repeated reads against pack 0,
count=6 -benchtime=1s, benchstat vs main):

    sec/op:    12.04 µs → 9.76 µs   -19.0%  (p=0.002, n=6)
    B/op:      4.91 KiB → 2.75 KiB  -44.1%  (p=0.002)
    allocs/op: 89       → 56         -37.1%  (p=0.002)

The hot-pack workload is the realistic case for any read
loop that walks a working set with locality (e.g. iterating
a tree). Without MRU, every read pays N/2 random
`MayContain` rejects before landing on the right pack;
with MRU the hint resolves it in O(1) on the fast path.

Assisted-by: Claude Opus 4.7
Signed-off-by: Hidde Beydals <hidde@hhh.computer>
H
Hidde Beydals committed
21391261b9e68a2ab034bed172cc7f2eeb4f9fcf
Parent: af25c06