Memory part 2: CPU caches之一

What every programmer should know about memory

Memory part 2: CPU caches--part1

原文链接http://lwn.net/Articles/252125/

       现在的CPU要比25年前要复杂得多。在那个时代,CPU核的频率和存储器总线频率是在一个水平。内存访问速度仅比寄存器访问慢一点。但是,这在90年代初发生了巨大的变化,当CPU设计者增加CPU核的频率时,内存总线的频率和RAM芯片的性能并没有同比例增加。这并不是由于不能制造更快的RAM,如前一节中解释的事实。这是可能的,但不经济。 和CPU核一样快的RAM比任何动态RAM都昂贵数个数量级。

       如果在使用非常小的,速度非常快的RAM的机器和有很多比较快的RAM的机器之间做出选择,在工作集大小超过小的ram和访问辅助存储介质如像硬盘驱动器情况下,那么第二台机器总是赢。这里的问题是访问二级存储的速度,通常是硬盘,必须用于保存部分换出的工作组。访问这些磁盘甚至比访问DRAM都要慢几个数量级。

       幸运的是,并不一定是全有或全无的决定。除了大量的DRAM,一台计算机可以有少量高速SRAM。一种可能的实现是把处理器的地址空间中的特定区域包含RAM,其余的包含DRAM。然后操作系统的任务是将数据最优分发到使用的SRAM上。本质上,SRAM在这种情况下的是作为处理器寄存器的一个扩展。

      虽然这是一个可能的实现,但是不可行。忽略映射物理资源如SRAM-backed内存到进程的虚拟地址空间(这本身是非常困难的)的问题,这种方案要求每个进程软件方式管理存储区分配。存储器区的大小随处理器不同而不同(即,处理器具有不同量的昂贵的SRAM支持的存储器)。组成程序的每个模块将要求其份额的快速内存,引入了同步的需求,这带来了额外的成本。总之,具有高速内存的收益将被管理资源的开销所抵消。

          因此,SRAM成为由处理器管理的一个透明使用的资源。而不是把它置于OS或用户的控制下。在这种模式下,SRAM用于主存的临时副本(换句话说,缓存), 这些数据可能很快被处理器使用。这是可能的,因为程序代码和数据的时间和空间局部性。这意味着,代码或数据在短的时间内将被重用有很大的可能性。对于代码,这意味着有最有可能的循环中的代码相同的代码被执行,并且再次(很好的例子,空间局部性)。理想情况下数据访问也仅限于小区域。虽然在很短的时间内使用的内存也不紧邻,不久之后同样的数据将被重用可能性很大(时间局部性)。对于代码,这意味着,例如,在一个循环中的一个函数调用是由位于该函数的地址空间中的其他地方。函数可能是在很远的内存中,但该函数的调用会在时间上接近。对于数据,它表示同一时间(工作集的大小)内存量总使用是被理想化的限定了,但所使用的内存,由于RAM的随机访问的特性,数据并没有紧密的在一起。认识到到局部性的存在是我们现在使用的CPU高速缓存的概念的关键。

        一个简单的计算,可以显示在理论上高速缓存如何有效。假设访问主存需要200个周期,高速缓存存储器的访问需要15个周期。 代码中使用100个数据单元且每个使用100次,如果没有高速缓存将要花费200万个周期, 如果所有的数据能被缓存则只需要168,500个周期。这是性能提高了91.5%。

        用于高速缓存的SRAM大小小于主存储器很多倍。依笔者的经验,工作站CPU高速缓存的大小, 通常大约是千分之一的主存储器的大小(目前:4MB缓存和4GB的主存)。这本身并不构成一个问题。如果工作集的大小(所述数据集的当前工作在之上)是小于高速缓存的大小,告诉缓存大小并不重要。但是计算机没有理由不会有大的主存储器。工作集势必会比高速缓存大。当系统运行多个进程,那么工作集的大小是所有单独的进程和内核的大小的总和, 工作集势必会比高速缓存大是肯定的。

处理的大小限制高速缓存需要什么, 需要的是一套很好的策略来决定在任何给定的时间什么应该被缓存。由于工集所有数据在同一时刻不是全部被使用,我们可以用技术来暂时用其他数据取代缓存中一些数据。也许这可以在数据实际需要之前做到。这种预取会减少一些访问主内存的花费,因为这种预取和执行程序异步同时发生的。所有这些技术以及更多的可以用来使缓存表现的比它实际上要大的。我们将在3.3节中讨论它们。一旦所有的这些技术都探究,那就轮到程序员来帮助处理器。在第6章中我们将要讨论如何可以做到这一点。

3.1 全局中的CPU缓存

在深入剖析CPU缓存实现技术细节之前,一些读者可能会发现首先看到的一些更详细的高速缓存如何融入现代计算机系统的“大画面”是很有用的。

19102912_X8oY.png

Figure 3.1: Minimum Cache Configuration

图3.1显示了最小的缓存配置。在可以发现的早期的体系结构CPU高速缓存的部署有相应的对应。 CPU核不再直接连接到主存储器。 {在更早的系统中,高速缓存被连接到系统总线,就像在CPU和主存储器。这是一个取巧(hack)而不是一个真正的解决方案}。所有加载和存储必须通过缓存。 CPU核心和缓存之间的连接是一种特殊的快速连接。以一种简化的表示,主存储器和高速缓存中连接到系统总线上,总线也可以用于与和其他系统组件的通信。我们推出了系统总线“FSB”,这是目前使用的名称,见第2.2节。在本节中,我们忽略了北桥芯片,它的存在被认为是为了方便主存储器和CPU(s)的通信。

在过去的几十年,尽管计算机已经使用了冯诺依曼结构,经验已经表明,代码和数据的高速缓存分开具有优势。直到1993年,Intel才使用独立的代码和数据高速缓存,并且永不回头。代码和数据所需的内存区域是几乎相互独立的,这就是为什么独立缓存的工作更好的原因。近年来出现了另一个优点:指令解码步骤对最大多数常见的处理器是缓慢的,用于解码的指令高速缓存可以加快执行速度,尤其管线由于不正确的预测或不可能预测分支而为空时。

在介绍缓存之后,系统变得更加复杂。高速缓存和主存储器之间的速度差再次增大,增大到某一个点另一个级别的高速缓存中被加进来,比第一级高速缓存的更大,更慢。只增加第一级高速缓存的大小,不是一种经济的选择。今天,甚至通常使用的机器有三个级别的高速缓存。有这样的处理器的系统看起来像图3.2。随着单一的CPU的核数的增加, 在未来高速缓存级的数目可能会增加的更多。

19102912_MjZz.png

Figure 3.2: Processor with Level 3 Cache

图3.2显示了三级缓存,并介绍了我们将在其它文件中使用的命名法。 L1D是1级数据缓存,L1I1级指令缓存,请注意,这是一个示意图,在现实中的数据流从核心到主存储器过程中不会经过高一级别的缓存。 CPU的设计者在告诉缓存的接口设计上有很多自由。对于程序员来说,这些设计选择是不可见的。

此外,我们有多核的处理器,每个核可以具有多个“线程”。一个核和线程之间的区别是不同的核有所有的硬件资源单独的副本({早期多核心处理器,甚至有单独的第二级缓存,并没有3级缓存。})。核可以完全独立地运行,除非它们都使用相同的资源,例如,在同一时间连接到外面。线程,在另一方面,几乎共享处理器的所有资源。英特尔实现的线程,只有独立的寄存器,甚至独立寄存器也是有限的,有些寄存器是共享的。因此,现代CPU的全景图看起来像图3.3。

19102913_3GT0.png

Figure 3.3: Multi processor, multi-core, multi-thread

在该图中,我们有两个处理器,每个有两个核,每一个都有两个线程。线程共享1级高速缓存。核(在暗灰色的阴影部分)有单独的1级高速缓存。CPU中的所有核同享更高级别的高速缓存。两个处理器(浅灰色阴影中的两个大箱子)当然不共享任何高速缓存。所有这一切都很重要的,尤其是当我们讨论缓存对多进程和多线程应用程序影响的时候。

3.2 更高称此的缓存操作

为了理解使用高速缓存的花费和节省, 我们需要结合计算机体系结构和第二节的RAM技术,以及上一节中所描述的高速缓存结构的指示。

默认情况下,CPU核读写的所有数据存储在高速缓存中。有些内存区域不能被缓存,但这只有是OS实现者才必须关注的,对于应用程序开发者来说它是不可见的。也有某些指令,允许程序员,故意绕过某些高速缓存。这些将在第6节中中讨论。

如果CPU需要一个数据字的高速缓存被首先搜索。显然,高速缓存不可能包含的整个主存储器的内容(否则我们就不再需要高速缓存),但因为所有的存储器地址都可高速缓存,使用在主存储器中的数据字的地址来给每个高速缓存条目进行标记。这样一个地址读取请求或写入请求可以在高速缓存搜索匹配的标签。在这种情况下, 基于缓存实现的的不同, 地址可以是虚拟的也可以是物理。

由于标签需要额外实际存储器的空间,字作为高速缓存的粒度选择是没有效率的。对于一个32位的字在x86机器上的标签本身可能需要32位或更多。此外,由于空间局部性是高速缓存的原则之一,不考虑到这一点显然是槽糕的。因为相邻的存储器很可能被一起使用,它应该一起加载到缓存中。也请记住,第2.2.1节中我们学到了什么:如果没有新的CASor甚至RAS信号,RAM模块输的一排数据字是更有效的。因此,存储在高速缓存中的条目不是单个的字,但是,相反,“行”的几个连续的字。在早期的高速缓存中,这些行是32个字节长,现在的标准是64个字节。如果存储器总线宽度是64位,这意味着每个高速缓存行需要8次传输。 DDR非常有效支持此传输模式。

当处理器存需要储器内容时,整个高速缓存行被加载到L1D。高速缓存行的地址是这样的,根据高速行大小进行掩码计算而得到。对于一个64字节的高速缓存行,这意味着低6位被清零。被丢弃的位被用作高速缓存线中的偏移量。剩余的位是在某些情况下,用于定位在高速缓存中的的行,以及作为标签。实际上一个地址值被分成三部分。对于一个32位的地址,它可能看起来如下:

19102913_yEi7.png

With a cache line size of 2O the low O bits are used as the offset into the cache line. The next S bits select the “cache set”. We will go into more detail soon on why sets, and not single slots, are used for cache lines. For now it is sufficient to understand there are 2S sets of cache lines. This leaves the top 32 - S - O = T bits which form the tag. These T bits are the value associated with each cache line to distinguish all the aliases {All cache lines with the same S part of the address are known by the same alias.} which are cached in the same cache set. The S bits used to address the cache set do not have to be stored since they are the same for all cache lines in the same set.

当一个高速缓存行的大小是2O  ,低O位被用作在高速缓存行中的偏移量。下一个S位选择“缓存集”。为什么是集合,而不是单个插槽,灯下我们将深入了解细节。现在只需要知道,有2S 的高速缓存行。这使得前32名 - S - O= T位形成的标记。这些T位的值与每个高速缓存行区分所有的别名高速缓存行的地址相同的S部分相同的别名}。这是在相同的缓存设置缓存。不必被存储,因为它们在相同组中的所有高速缓存行是相同的用于处理缓存集S位。

当一个指令修改内存的处理器首先要加载一个缓存行,因为没有指令可以立马修改整个缓存行(这条规则的例外:第6.1节中解释写结合)。因此,高速缓存行的内容必须在写操作之前被加载。告诉缓存不可能的持有部分的高速缓存行。已经被写入,并且还没有被写回到主存储器的高速缓存行被认为是“脏”。一旦被写入的,脏标志被要被清除。

为了能够加载新数据到缓存中,几乎总是首先要确认高速缓存有空间。从L1D移除的的高速缓存行推入到L2(使用相同的高速缓存行的大小)。当然,这意味着必须在L2空间有空间。依次,这可能会推到L3,最终到主内存中。每个移除越来越昂贵。这里所描述的是一个独占高速缓存模型,为现代AMD和威盛处理器采用。英特尔实现包容性高速缓存。{这种概括是不完全正确的。有些高速缓存是排斥的,有些包容性高速缓存具有独占缓存属性。},其中每个高速缓存行在L1D中 也在L2中也存在。因此,驱逐从L1D的速度要快得多。有了足够的L2高速缓存,内存内容保存在两个地方举行的浪费的缺点是微不足道的,移除付出总有回报的。独家高速缓存的一种可能的优点是,仅装载一个新的高速缓存行只需要L1D,而不需要L2,这可能会更快。

允许的CPU一直管理高速缓存,只要处理器体系结构中定义的存储器模型不变。确实是,例如,完美精致的处理器,利用很少或根本没有内存总线活动,并主动把脏高速缓存行写回主内存。x86和x86-64的各种各样的处理器的高速缓存架构,制造商之间,即使在同一制造商生产的产品,证明了的内存模型抽象的力量。

在对称多处理器(SMP)系统中的CPU的高速缓存不能彼此独立地工作。所有的处理器在任何时候都应该看到相同的内存内容。维护这个统一的内存视图被称为“高速缓存一致性”。如果一个处理器只简单的关注自己的高速缓存和主内存,它不会看到在其他处理器上的脏高速缓存行的内容。提供从一个处理器的高速缓存另一个处理器将直接访问是非常昂贵的,并且是一个巨大的瓶颈。相反,当另一个处理器要读取或写入到在一定的高速缓存行,那么处理器要进行检测。

如果一个写访问被检测到,该处理器在其高速缓存中有一个的高速缓存行干净的副本,该高速缓存行被标记为无效。以后将需要重新加载缓存行。注意,另一个CPU上的写访问不需要设置一个无效,多个干净的副本可以很好地保持。

更先进的高速缓存实现允许另一种可能性发生。如果第一个处理器的缓存当前标记为脏,另一个处理器要读取或写入这个高速缓存行,那么不同的行动方针是必要的。在这种情况下的主存储器已经过时,当前正在请求的处理器,必须而且应该,从所述第一处理器得到高速缓存行的内容。通过侦听,所述第一处理器注意到这种情况,并自动给请求处理器发送数据。此操作绕过主存储器,尽管在一些实施方式中,存储器控制器应该注意到这种直接传输,会把更新的高速缓存线的内容存储到主存储器中。如果写第一个处理器的访问,那么本地高速缓存行副本的置无效。

随着时间的推移,已经出一些开发高速缓存一致性协议。最重要的是MESI,我们将在第3.3.4节介绍。这一切的成果可以归纳为几个简单的规则:

  • 一个脏缓存行不存在任何其他处理器的高速缓存中。
  • 同一高速缓存行的干净的副本可以驻留在任意多个高速缓存中。

如果这些规则能够遵守,即使在多处理器系统中中处理器也可以有效地使用他们的高速缓存。所有的处理器需要做的就是监控彼此的写访问,并把那些(写访问)地址和本地高速缓存的地址做比较。在下一节中,我们将进入探讨一些实现的细节,特别是成本。

最后,我们至少应该给出高速缓存命中和未命中的相关的成本。这是英特尔奔腾M列出的数字:

To WhereCycles
Register<= 1
L1d~3
L2~14
Main Memory~240

这些都是实际测量的访问时间在CPU周期。需要注意有趣的一点是,访问片上的L2高速缓存的时间的很大一部分(甚至可能是大多数)由线路延迟的延迟导致的。这是一个物理的限制,当增加高速缓存的大小会变得更糟。只有处理缩小(例如,在英特尔的系列的,从60纳米的Merom处理器到45纳米的Penryn处理器)才可以改善这些数字。

表中的数字看起来很高,但幸运的是,整个成本不必须负担每次出现的缓存加载和不命中。某些部分的成本可以被隐藏。现在的处理器都使用不同长度的内部流水线,在管道内指令被解码,准备好执行。如果一个值是传送到一个寄存器,那么部分的准备工作是从存储器(或高速缓存)加载数据值。如果内存加载操作可以很早在流水线中进行,它可以与其他操作并行发生,且全部的花费可能会被隐藏。对L1D往往如此;某些有长输管道的处理器的L2也可以。

存储器读初有许多障碍。它可能只是简单的不具有足够资源供存储器访问,或者由于另一个指令后导致对最终地址的加载变得可用。在这种情况下,加载成本是不能隐藏(完全)。

对于写操作,CPU并不一定必须一直等待,直到该值被安全地存储在内存中。只要接下来的指令执行效果和值被存储在存储器一样,就没有什么防止CPU走捷径。它可以提前执行下一条指令。影子寄存器可以保存常规寄存器不可用的值,在影子寄存器的帮助下,它(CPU)甚至有可能改变存储在不完整的写操作的值。

19102913_OjEl.png

Figure 3.4: Access Times for Random Writes

为了说明缓存活动的影响,请见图3.4。我们稍候将讨论产生数据的程序,它是一个以随机方式重复访问一个可配置的内存总量简单的模拟程序。每个数据项的具有固定大小。元素的数目取决于所选择的工作集大小。 Y轴显示平均的CPU周期数,注意为Y-轴是衡量标准是对数。这同样适用于这种类型的所有的图表的X轴。工作组的大小始终以2的幂来显示。

图中显示了三种不同的平滑区段。这并不令人惊讶:这个处理器有L1D和L2高速缓存,没有L3。依据一些经验,我们可以推断出,L1D为213个字节个大小和L2的大小是220 字节。如果整个工作集大小匹配L1D,则每个数据单元的每次操作低于10。 一旦超过L1D大小,处理器就得从L2加载数据,平均时间就会猛增到28。一旦L2也不能满足, 时间就会跳到480个时钟或更多。这时许多或大部分的操作,必须从主内存加载数据。更糟糕的是,因为数据被修改的脏缓存行必须写回。

此图给予了足够的理由让我们来窥视有助于提高高速缓存利用率的代码的改进。在这里我们不是在谈论几个可怜的百分比,我们谈论的是有时有可能数个数量级的改善。在第6章中,我们将讨论能使我们编写更高效代码的技术。下一节探究更多CPU高速缓存设计细节。这些知识是有益的,但对于这篇文章来说不是必要。因此,这部分可以被跳过。


转载于:https://my.oschina.net/daleshen128/blog/70421

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值