上篇文章中我提到了数组和链表,说了为什么数组要求元素是相同类型的而链表却没有要求,这篇文章我会从数组和链表的不同来看待内存和缓存。
首先说说什么内存和缓存,内存和缓存是计算机中的存储设备,它们的作用分别是:内存用于临时存储程序运行中正在处理的数据,而缓存则用于存储经常访问的数据和指令,缓存是为了解决cpu读取速率和内存读取速率不匹配的问题。
从内存利用方面看,首先内存是有限的,而且一块内存不能被多个程序共享,所以我们希望能够尽可能地提高内存的利用率。对数组来说,数组是一块连续的内存空间,元素紧密排列,空间效率更高,但是一次性分配内存也可能导致内存浪费,而且在数组进行扩容时也需要时间和空间成本。而对于链表来说,链表是由节点组成,节点之间通过引用(指针)连接,在添加和删除元素时只需要改变指针的指向就行,以节点为单位进行动态内存分配和回收,灵活并且插入和删除效率高,但是需要额外的空间来存储指针,在频繁的插入和删除时更容易导致内存碎片化。
从缓存方面看,虽然缓存在空间容量上远小于内存,但它比内存快得多。由于缓存的容量有限,只能存储一小部分频繁访问的数据,因此当 CPU 尝试访问的数据不在缓存中时,就会发生缓存未命中 ,意思就是缓存中没有你cpu需要的数据。所以此时 CPU 不得不从速度较慢的内存中加载所需数据。显然对系统来说,缓存未命中越少,CPU 读写数据的效率就越高
,程序性能也就越好。所以我们将 CPU 从缓存中成功获取数据的比例称为缓存命中率 ,这个指标通常用来衡量缓存效率。
为了尽可能达到更高的效率,缓存会采取以下数据加载机制。
‧
缓存行
:缓存不是单个字节地存储与加载数据,而是以缓存行为单位。相比于单个字节的传输,缓存行 的传输形式更加高效。
‧
预取机制
:处理器会尝试预测数据访问模式(例如顺序访问、固定步长跳跃访问等),并根据特定模式 将数据加载至缓存之中,从而提升命中率。
‧
空间局部性:如果一个数据被访问,那么它附近的数据可能近期也会被访问。因此,缓存在加载某一数据时,也会加载其附近的数据,以提高命中率。
‧
时间局部性
:如果一个数据被访问,那么它在不久的将来很可能再次被访问。缓存利用这一原理,通过保留最近访问过的数据来提高命中率。
但是实际上,
数组和链表对缓存的利用效率是不同的
,主要体现在以下几个方面。
‧
占用空间
:链表元素比数组元素占用空间更多,导致缓存中容纳的有效数据量更少。
‧
缓存行
:链表数据分散在内存各处,而缓存是“按行加载”的,因此加载到无效数据的比例更高。
‧
预取机制
:数组比链表的数据访问模式更具“可预测性”,即系统更容易猜出即将被加载的数据。
‧
空间局部性
:数组被存储在集中的内存空间中,因此被加载数据附近的数据更有可能即将被访问。
总体而言,
数组具有更高的缓存命中率,因此它在操作效率上通常优于链表
。