2026-07-17
Classic CLOCK gives you LRU-ish behavior at hardware speed: a circular buffer of entries, each with a single reference bit. On access, set the bit to 1. On eviction, the hand sweeps forward, clearing bits it finds set and evicting the first entry it finds with a 0. Cheap, lock-friendly, decent hit rates. But that single bit is binary — an entry hit once looks identical to one hit ten thousand times.
GCLOCK (Generalized CLOCK) replaces the reference bit with a small counter, typically 2-8 bits. On access, increment the counter (usually saturating at the max). On eviction, the hand decrements each counter it passes; only entries at zero are evicted. Hot entries survive multiple sweeps of the hand — they've built up "credit" that must be spent before they're vulnerable.
Why it beats plain CLOCK: in workloads with skewed access (a few hot keys, many cold ones), CLOCK evicts hot entries during scans because its bit tops out at 1. GCLOCK's counter preserves the frequency signal that CLOCK throws away, giving hit rates closer to LFU while keeping CLOCK's O(1) amortized cost and lock-friendliness.
Real-world example: PostgreSQL's buffer manager uses a GCLOCK variant. Each buffer has a usage_count capped at 5. When a backend needs a free buffer, the clock sweep decrements usage_count on each pinned-then-released buffer; only when it hits 0 is the buffer eligible for eviction. A frequently-touched index root page can survive five full sweeps before it's even a candidate — critical for keeping hot metadata resident under scan-heavy workloads like nightly analytics reports scanning cold tables.
Rule of thumb for counter width: use log₂(expected sweep-to-access ratio for hot items) + 1 bits. If your hottest keys get touched ~100× between sweeps, you'd want ~7 bits. In practice, 2-3 bits is the sweet spot — enough to distinguish hot from warm, small enough that decrementing on sweep still ages entries in reasonable time. PostgreSQL's cap of 5 (needs 3 bits) reflects decades of tuning.
The trade-off: GCLOCK spends more work per eviction than CLOCK. The sweep may pass over the same entry many times before finding a victim. Under memory pressure with mostly-hot entries, eviction latency can spike as the hand grinds down counters. Mitigations: cap the counter increment (don't let one page hit +1000), or bound the sweep length and fall back to random eviction if the hand travels too far.
If you're building a cache and CLOCK's simple bit isn't preserving enough frequency information, GCLOCK is usually the next step before jumping to something like TinyLFU.
