Answers:
如果包含正在加载的字节或单词的高速缓存行尚未存在于高速缓存中,则您的CPU将请求从高速缓存行边界开始的64个字节(所需最大地址以下的最大地址为64的倍数) 。
现代PC内存模块一次传输64位(8字节),一次传输八次,因此一个命令触发从内存中读取或写入整个高速缓存行。(DDR1 / 2/3/4 SDRAM突发传输大小最多可配置为64B; CPU将选择突发传输大小以匹配其缓存行大小,但是64B是常见的)
根据经验,如果处理器无法预测内存访问(并预取),则检索过程可能需要约90纳秒或约250个时钟周期(从CPU知道地址到接收数据的CPU)。
相比之下,在现代x86 CPU上,L1高速缓存的命中具有3或4个周期的加载使用延迟,而存储重载具有4或5个周期的存储转发延迟。在其他体系结构上情况相似。
进一步阅读:乌尔里希·德雷珀(Ulrich Drepper) 《每个程序员应该了解的内存》。软件预取建议有些过时:现代的硬件预取器更智能,并且超线程比P4时代要好(因此预取线程通常是浪费的)。另外,x86 标签Wiki具有该体系结构的许多性能链接。
如果高速缓存行为64字节宽,则它们对应于以64整除的地址开始的内存块。任何地址的最低6位都是高速缓存行的偏移量。
因此,对于任何给定的字节,可以通过清除地址的最低有效六位来找到必须提取的高速缓存行,这对应于舍入到最接近的可被64整除的地址。
尽管这是通过硬件完成的,但我们可以使用一些参考C宏定义来显示计算:
#define CACHE_BLOCK_BITS 6
#define CACHE_BLOCK_SIZE (1U << CACHE_BLOCK_BITS) /* 64 */
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1) /* 63, 0x3F */
/* Which byte offset in its cache block does this address reference? */
#define CACHE_BLOCK_OFFSET(ADDR) ((ADDR) & CACHE_BLOCK_MASK)
/* Address of 64 byte block brought into the cache when ADDR accessed */
#define CACHE_BLOCK_ALIGNED_ADDR(ADDR) ((ADDR) & ~CACHE_BLOCK_MASK)
0b1000000
注意到,最后6位是零,所以即使您有一些数字与这6个集合中的任何一个(代表数字, %64),清除它们将为您提供最接近的64字节对齐内存地址。
首先,主存储器访问非常昂贵。当前,一个2GHz的CPU(最慢的一次)每秒具有2G的滴答声(周期)。一个CPU(现今的虚拟内核)可以在每个周期内从其寄存器中获取一个值。由于虚拟内核由多个处理单元(ALU-算术逻辑单元,FPU等)组成,因此,如果可能,它实际上可以并行处理某些指令。
主存储器的访问成本约为70ns至100ns(DDR4稍快一些)。这次基本上是在查找L1,L2和L3高速缓存,然后击中内存(将命令发送到内存控制器,然后将其发送到内存库),等待响应并完成。
100ns表示约200个滴答声。因此,基本上,如果程序始终会丢失每个内存访问的缓存,则CPU将花费其99.5%的时间(如果仅读取内存)空闲等待内存。
为了加快速度,这里有L1,L2,L3缓存。他们使用直接放置在芯片上的存储器,并使用不同类型的晶体管电路来存储给定的位。因为通常使用更先进的技术来生产CPU,并且L1,L2,L3存储器的生产故障有可能使CPU一文不值(缺陷),所以这比主存储器占用更多的空间,更多的能量并且成本更高。大的L1,L2,L3高速缓存会增加错误率,从而降低良率,从而直接降低ROI。因此,在可用缓存大小方面要进行巨大的权衡。
(当前,人们创建了更多的L1,L2,L3高速缓存,以便能够停用某些部分,以减少实际生产缺陷是高速缓存存储区整体上导致CPU缺陷的机会)。
给出定时想法(来源:访问缓存和内存的成本)
由于我们混合使用不同的CPU类型,这些只是估计值,但是可以很好地了解在获取内存值时的实际情况,并且在某些高速缓存层中可能会命中或未命中。
因此,缓存基本上可以大大提高内存访问速度(60ns与1ns)。
取值,将其存储在缓存中以便重新读取它对于经常访问的变量来说是好的,但是对于内存复制操作,它仍然会变慢,因为一个人只能读取一个值,将其写入某个地方而从不读取该值再次...没有缓存命中,非常缓慢(此外,由于我们的执行顺序混乱,因此可以并行发生)。
此内存副本非常重要,以至于有不同的方法可以加快它的速度。在早期,内存通常能够在CPU外部复制内存。它由内存控制器直接处理,因此内存复制操作不会污染缓存。
但是,除了普通的内存副本外,其他串行内存访问也很常见。一个示例是分析一系列信息。拥有整数数组并计算总和,均值,平均值或更简单的方法是找到某个值(过滤器/搜索)是每次在任何通用CPU上每次运行时都非常重要的一类算法。
因此,通过分析内存访问模式,很明显,数据被频繁地顺序读取。如果程序读取索引i处的值的可能性很高,那么该程序也将读取i + 1值。此概率略高于同一程序还将读取值i + 2的概率,依此类推。
因此,给定一个内存地址,预先读取并获取其他值是(现在仍然是)一个好主意。这就是为什么存在升压模式的原因。
提升模式下的内存访问意味着发送一个地址并顺序发送多个值。每个附加值发送仅花费大约10ns(甚至更低)。
另一个问题是地址。发送地址需要时间。为了寻址存储器的大部分,必须发送大地址。在早期,这意味着地址总线的大小不足以在单个周期(刻度)内发送地址,并且需要多个周期来发送地址,从而增加了更多延迟。
例如,一个64字节的高速缓存行意味着将内存划分为大小为64字节的不同(非重叠)内存块。64字节表示每个块的起始地址具有最低的六个地址位,始终为零。因此,对于任何数量的地址总线宽度,都不需要每次发送这六个零位,从而将地址空间增加64倍(欢迎效果)。
高速缓存行解决的另一个问题(除了预先读取并保存/释放地址总线上的六位以外)是高速缓存的组织方式。例如,如果将高速缓存划分为8个字节(64位)的块(单元),则需要存储该高速缓存单元与之一起保存值的存储单元的地址。如果该地址也为64位,则意味着该地址消耗了一半的高速缓存大小,导致开销为100%。
由于高速缓存行为64字节,而CPU可能使用64位-6位= 58位(无需太正确地存储零位),这意味着我们可以以58位(开销为11%)的开销来缓存64字节或512位。实际上,存储的地址甚至比这个要小,但是有状态信息(例如,高速缓存行有效且准确,肮脏并且需要写回ram等)。
另一个方面是,我们具有集合关联缓存。并非每个缓存单元都能够存储某个地址,而只能存储其中的一部分。这使得必要的存储地址位更小,允许并行访问缓存(每个子集可以访问一次,但独立于其他子集)。
尤其是要同步不同虚拟内核之间的缓存/内存访问,每个虚拟内核具有独立的多个处理单元,最后在一个主板上具有多个处理器(这些主板可容纳多达48个处理器,甚至更多)。
这基本上就是我们拥有高速缓存行的当前想法。预先读取的好处非常高,最糟糕的情况是从高速缓存行中读取单个字节,然后再也不读取其余字节,因为这种可能性非常小。
高速缓存行的大小(64)是较大的高速缓存行之间的明智选择,这使得它的最后一个字节不太可能在不久的将来读取,即获取完整高速缓存行所需的时间从内存(并将其写回),以及缓存组织中的开销以及缓存和内存访问的并行化。
我不能肯定地说,因为每个硬件都不同,但是通常这是“ 64字节从下面最接近的64字节边界开始”,因为这对于CPU来说是非常快速和简单的操作。