1. 链表的内存存储方式
- 链表的非连续内存分配:在链表中,每个节点包含了数据和指向下一个节点的指针(对于双向链表,还包括指向上一个节点的指针)。这些节点在内存中并不是连续存储的,可能分布在不同的内存位置。
- 指针跳跃访问:由于链表节点分散存储,CPU 访问一个节点时需要通过指针跳转到下一个节点,导致访问时无法利用 CPU 的缓存局部性(cache locality)。而数组中的元素是按顺序存储的,CPU 在访问时可以预加载相邻元素,从而提高缓存命中率。
2. CPU 缓存与局部性原理
- 缓存局部性:CPU 在执行程序时会预取接下来的数据,特别是当程序按顺序访问内存时,CPU 会尽量将接下来的数据提前加载到高速缓存中。连续的内存块会增加缓存命中的概率,因为它们存储在相邻的内存位置,易于被一起加载到缓存中。
- 链表的指针跳跃与缓存不匹配:由于链表中的元素分散存储,CPU 在访问链表时无法通过顺序的内存访问来提升缓存命中率。例如,当访问链表的一个节点时,CPU 需要加载当前节点的数据,然后通过指针去查找下一个节点的内存地址,这种跳跃式的访问使得 CPU 无法有效地预加载下一个节点的数据,进而导致缓存未命中。
3. CPU 缓存的缓存行(Cache Line)
- 缓存行的概念:现代 CPU 的缓存系统是按“缓存行”来组织的,通常每个缓存行的大小是 64 字节。数组元素是连续存储的,因此一旦加载到缓存行中,紧随其后的元素也很可能已经被加载到缓存中,进而提高了连续访问的效率。
- 链表的访问模式与缓存行不匹配:链表元素是分散存储的,访问一个节点时,可能会导致 CPU 从不同的内存地址加载数据,这就无法有效利用缓存行的优势。尤其是当链表节点存在于不同的内存页或缓存行时,缓存的有效性就大大降低。