Separate Chaining vs Linear Probing: Cache Differences?
Separate chaining and open addressing with linear probing handle hash collisions in fundamentally different ways, leading to significant differences in CPU cache performance. While separate chaining relies on linked lists or secondary structures that scatter data across memory, linear probing stores all entries in a single contiguous array. Because modern processors fetch memory in fixed-size cache lines rather than individual bytes, linear probing typically achieves much higher cache hit rates and faster lookup times under typical load factors.
Memory Layout and Spatial Locality
In separate chaining, each bucket in the hash table holds a pointer to a linked list (or another dynamic container) of entries that hash to the same index. When a collision occurs, a new node is usually allocated on the heap and linked to the existing chain. Because heap allocations are rarely contiguous, traversing a chain requires following pointers to disparate memory addresses. Each pointer dereference risks a cache miss, forcing the CPU to stall while fetching a new cache line from main memory.
In contrast, open addressing with linear probing stores all key-value pairs directly inside a single contiguous array. When a collision occurs at index \(h(k)\), the algorithm checks consecutive slots: \(h(k) + 1\), \(h(k) + 2\), and so on, wrapping around the array. Because adjacent array elements reside in the same or neighboring cache lines (typically 64 bytes), probing multiple slots often requires only a single cache line fetch.
Cache Line Utilization and Prefetching
Modern CPUs are heavily optimized for sequential memory access. When linear probing reads an entry at index \(i\), the hardware loads the entire cache line containing \(i\) into the L1 cache. Subsequent probes to \(i + 1\) or \(i + 2\) hit the L1 cache directly, incurring minimal latency. Furthermore, hardware prefetchers can detect sequential access patterns and load upcoming cache lines before they are requested.
Separate chaining cannot benefit from hardware prefetching during collision resolution because the next memory address in a linked list is unknown until the current node's pointer is loaded and dereferenced. This serialization of memory reads—often called pointer chasing—exposes the full latency of main memory on every cache miss.
Impact of Load Factor and Clustering
The cache advantage of linear probing depends on the load factor \(\alpha = \frac{n}{m}\), where \(n\) is the number of stored elements and \(m\) is the table capacity. At low to moderate load factors (such as \(\alpha \le 0.5\) or \(\alpha \le 0.7\)), probe sequences remain short, and most lookups complete within one or two cache lines.
However, as \(\alpha\) approaches \(1\), linear probing suffers from primary clustering, where long runs of occupied slots form. The expected number of probes for an unsuccessful search grows roughly as \(\frac{1}{2}\left(1 + \frac{1}{(1 - \alpha)^2}\right)\), which can span multiple cache lines and degrade performance. Separate chaining degrades more gracefully at high load factors, with expected chain length equal to \(\alpha\), though each step in the chain still incurs a potential cache miss.
Summary of Trade-offs
Linear probing generally outperforms separate chaining in classical computing environments due to superior spatial locality, reduced memory overhead from avoiding pointers, and better utilization of CPU cache lines. Separate chaining remains useful when the load factor is high, when entries are very large (making contiguous array storage expensive to resize), or when stable pointers to elements are required across insertions and deletions.