Summary
CCoinsCacheEntry keeps its DIRTY/FRESH state in a separate uint8_t m_flags. Since the struct also holds two CoinsCachePair* list pointers, that one byte forces 7 bytes of trailing padding - so the flags cost 8 bytes per cached UTXO.
CoinsCachePair is pointer-aligned (alignment >= 8), so the low 3 bits of the m_prev pointer are always zero. This PR packs the 2 flag bits into those low bits and removes the m_flags member, funnelling all flag/list access through four small private helpers (GetFlags / PrevPtr / SetPrevPtr / SetFlagBits). The linked-list logic is otherwise unchanged.
sizeof(CCoinsCacheEntry): 80 -> 72 bytes- pool-allocator block: 152 -> 144 bytes
No on-disk or consensus format change.
Motivation
The UTXO cache is the dominant RAM consumer during IBD, and reducing bytes-per-coin lets a given -dbcache hold more coins and flush less often. This helps memory-constrained / low-end nodes in particular.
Measurements
Memory (mainnet IBD, identical config, compared at matching heights via the cache=<MiB>(<txo>) figure in the UpdateTip log = CoinsTip().DynamicMemoryUsage()):
| height | stock B/UTXO | patched B/UTXO | reduction |
|---|---|---|---|
| 150000 | 150.1 | 142.5 | -5.1% |
| 180000 | 153.1 | 145.3 | -5.1% |
| 200000 | 147.8 | 139.9 | -5.3% |
CPU (bench_bitcoin -filter=CCoinsCaching, interleaved runs): stock mean 199.1 ns/op vs patched 200.1 ns/op - within run-to-run noise; no measurable regression from the masking. (Run on a laptop with ~15% variance; happy to have reviewers re-run on a quiet machine.)
Testing
coins_tests, coinscachepair_tests, validation_flush_tests pass unchanged.
Notes / open questions
- The flagged-list design was introduced in #28280; this builds directly on its encapsulated flag access.
- Trade-off for review: 8 bytes/coin vs. a small amount of bit-masking on flag access. Benchmarks above suggest the masking is free in practice, but reviewers may weigh the added indirection.