try ai
科普
编辑
分享
反馈
  • 有效内存访问时间

有效内存访问时间

SciencePedia玻尔百科
核心要点
  • 有效内存访问时间 (EMAT) 是平均访问延迟,其计算方法为缓存命中时间加上未命中率与未命中惩罚的乘积。
  • 多级缓存层次结构利用程序行为中的局部性原理,弥合了 CPU 与主存之间的速度鸿沟。
  • 设计内存系统涉及根本性的权衡,例如在更低的缓存命中时间和更低的缓存未命中率之间取得平衡,以最小化整体 EMAT。
  • 最优性能是通过硬件与软件的协作实现的,其中算法和数据结构的设计需要与缓存及 TLB 层次结构协同工作。

引言

在现代计算世界中,中央处理器(CPU)的惊人速度与主存相对迟缓的步伐之间存在着巨大的鸿沟。这种性能差距通常被称为“内存墙”,意味着即使是最强大的处理器,也有相当一部分时间处于空闲状态,仅仅是为了等待数据到达。这个根本性的瓶颈提出了一个关键问题:如果 CPU 总是处于停滞状态,我们如何才能构建更快的计算机?

本文通过深入探讨计算机体系结构的核心——内存层次结构这一优雅的解决方案,来应对这一挑战。计算机并非使用单一、缓慢的数据仓库,而是利用一系列更小、更快的缓存,将重要信息置于手边。我们将探讨有效内存访问时间 (EMAT) 的核心概念,这一指标使我们能够量化该系统的性能。在接下来的章节中,您将深入了解使该系统得以运作的原理,以及这一强大理念的深远应用。

第一部分“原理与机制”将解构内存层次结构,解释缓存如何运作,并推导基础的 EMAT 公式。我们将审视支撑这一切的关键——局部性原理,并探索一个内存请求的复杂旅程,从通过 TLB 进行虚拟地址转换到最终的数据检索。随后,“应用与跨学科联系”部分将展示 EMAT 框架如何成为解锁性能的一把万能钥匙,指导着从微芯片设计、软件优化到并行和虚拟化系统中复杂协作的方方面面。

原理与机制

巨大的鸿沟:CPU 速度与内存延迟

想象你是一位出色的厨师,能够以闪电般的速度切菜、调味和烹饪。你的厨房就是中央处理器(CPU),计算的核心。但有一个问题:你所有的食材都储存在几条街外的一个巨大仓库里——主存,即 DRAM。每次你需要一根胡萝卜,你都必须停下手中的一切,跑到仓库,找到胡萝卜,然后跑回来。无论你在厨房里有多快,你整体的烹饪速度都受制于这些缓慢而乏味的仓库往返之旅。

这就是现代计算面临的根本挑战。几十年来,CPU 的速度变得惊人地快,而主存的速度却远远落后。这个日益扩大的差距,通常被称为“内存墙”,意味着处理器大部分时间都在等待数据到达而处于空闲状态。如果我们的厨师总是在等待食材,我们怎么可能造出一台快速的计算机呢?解决方案不是让仓库变得更快——那极其昂贵。解决方案是变得更聪明。

一个袖珍记事本:缓存的魔力

一个真正的厨师会怎么做?他们不会为每一根胡萝卜都跑去仓库。在开始工作前,他们会预估需要什么,并在身边的操作台上准备一小部分常用食材。这个小巧、快速且近便的存储就是​​高速缓存 (cache)​​。

计算机的缓存是放置在 CPU 旁边的一小块非常快速、昂贵的内存。当 CPU 需要一条数据时,它首先检查这个缓存。如果数据在那里(一次​​缓存命中​​),它几乎可以立即获取。如果数据不在那里(一次​​缓存未命中​​),CPU 就必须长途跋涉去主存,但它会做一个聪明的举动:它不仅带回所请求的数据项,还会带回一大块附近的数据,因为它假设可能很快会需要这些数据。

这种安排提出了一个关键问题:我们的厨师获取一份食材的平均时间是多少?这就是​​平均内存访问时间 (AMAT)​​。它既不是快速的命中时间,也不是缓慢的未命中时间;它是两者的混合,并按各自发生的频率加权。

让我们从第一性原理来思考这个问题。假设未命中的概率是 mmm(​​未命中率​​)。那么命中的概率就是 1−m1-m1−m。命中所花费的时间是​​命中时间​​,我们称之为 ThitT_{hit}Thit​。未命中时花费的时间是命中时间(检查缓存并失败所花的时间)加上去主存的额外时间,我们称之为​​未命中惩罚​​,TpenaltyT_{penalty}Tpenalty​。

平均时间是每种结果的时间乘以其概率的总和:

AMAT=P(hit)×T(hit)+P(miss)×T(miss)\mathrm{AMAT} = P(\text{hit}) \times T(\text{hit}) + P(\text{miss}) \times T(\text{miss})AMAT=P(hit)×T(hit)+P(miss)×T(miss)
AMAT=(1−m)⋅Thit+m⋅(Thit+Tpenalty)\mathrm{AMAT} = (1-m) \cdot T_{hit} + m \cdot (T_{hit} + T_{penalty})AMAT=(1−m)⋅Thit​+m⋅(Thit​+Tpenalty​)

如果我们重新整理这个式子,一个优美的简洁形式便出现了:

AMAT=Thit−m⋅Thit+m⋅Thit+m⋅Tpenalty\mathrm{AMAT} = T_{hit} - m \cdot T_{hit} + m \cdot T_{hit} + m \cdot T_{penalty}AMAT=Thit​−m⋅Thit​+m⋅Thit​+m⋅Tpenalty​
AMAT=Thit+m⋅Tpenalty\mathrm{AMAT} = T_{hit} + m \cdot T_{penalty}AMAT=Thit​+m⋅Tpenalty​

这个优雅的公式是性能分析的基石。它告诉我们,平均时间是最佳情况下的时间(ThitT_{hit}Thit​)加上一个惩罚项,该惩罚项取决于我们失败的频率(mmm)以及失败的后果有多严重(TpenaltyT_{penalty}Tpenalty​)。为了提升性能,我们可以加快命中时间、降低未命中率或减少未命中惩罚。

缓存层层相套

如果一个小记事本好用,为什么不加一个稍微大一点的呢?这正是现代计算机所做的。它们使用​​内存层次结构​​。紧挨着 CPU 的是一个极小、超快的 Level 1 (L1) 缓存。稍远一点是一个更大、略慢的 Level 2 (L2) 缓存。然后,更远的地方,可能还有一个 Level 3 (L3) 缓存,只有在那之后我们才去主存“仓库”。

这如何改变我们的 AMAT 计算?这个公式的美妙之处在于它可以递归应用。L1 缓存的“未命中惩罚”就是从 L2 缓存获取数据所需的时间。但那个时间是多少呢?它就是 L2 缓存的 AMAT!

假设 L1 的命中时间为 Th1T_{h1}Th1​,未命中率为 m1m_1m1​。它的未命中惩罚 MP1MP_1MP1​ 是访问 L2 的时间。一次 L2 访问有它自己的命中时间 Th2T_{h2}Th2​ 和它自己的局部未命中率 m2m_2m2​。它的未命中惩罚 MP2MP_2MP2​ 是访问主存的时间 TmemT_{mem}Tmem​。

所以,L2 的 AMAT 是: AMATL2=Th2+m2⋅Tmem\mathrm{AMAT}_{L2} = T_{h2} + m_2 \cdot T_{mem}AMATL2​=Th2​+m2​⋅Tmem​

这整个表达式就是 L1 的未命中惩罚!将它代入我们的主公式: AMAT=Th1+m1⋅MP1=Th1+m1⋅(Th2+m2⋅Tmem)\mathrm{AMAT} = T_{h1} + m_1 \cdot MP_1 = T_{h1} + m_1 \cdot (T_{h2} + m_2 \cdot T_{mem})AMAT=Th1​+m1​⋅MP1​=Th1​+m1​⋅(Th2​+m2​⋅Tmem​) 这种层次结构 是计算机设计中一个深刻且反复出现的主题。层次结构中的每一级都试图为上一级屏蔽下一级的延迟。架构师甚至发明了像“命中下未命中 (hit-under-miss)”这样的巧妙技巧,即在 L2 服务一个耗时的 L1 未命中的同时,处理器继续处理来自 L1 缓存的命中,从而有效地隐藏了部分未命中惩罚。

秘诀:局部性原理

如果内存访问是完全随机的,那么这整个缓存层次结构方案将毫无用处。如果厨师需要一系列随机的食材——一颗花椒,然后是仓库另一头的一只龙虾,再然后是一粒米——那么操作台上的小记事本也帮不上什么大忙。

缓存之所以有效,是因为程序的行为不是随机的。它们表现出​​局部性原理​​。

  1. ​​时间局部性(Locality in Time):​​ 如果你访问了一块数据,你很可能在不久的将来再次访问它。想一下循环计数器变量。
  2. ​​空间局部性(Locality in Space):​​ 如果你访问了一块数据,你很可能在不久的将来访问其附近内存地址的数据。想一下遍历一个数组。

当发生缓存未命中时,系统会围绕被请求的数据项取回一整块数据(例如 64 字节)。这利用了空间局部性。后续对同一块中其他数据项的请求将是闪电般快速的 L1 命中。

时间局部性是拥有缓存之所以有帮助的根本原因。如果程序不断重复使用相同的数据,这些数据将保留在缓存中,访问就会很快。考虑一个程序反复扫描一个大数组。如果整个数组能装入 L2 缓存,第一次遍历会很慢,因为它需要将所有数据从主存拉入 L2。但随后的每一次遍历都会快得多,因为所有 L1 未命中现在都变成了 L2 命中。L2 缓存捕获了遍历之间的时间局部性。然而,如果数组大于 L2 缓存,这种好处就消失了。每一次遍历都会驱逐前一次遍历的数据,每一次 L1 未命中都会导致一次昂贵的到主存的访问,就好像 L2 根本不存在一样。因此,性能是程序​​工作集​​(其活动数据)的大小与缓存大小之间的一场精妙舞蹈。

架构师的交易:没有免费的午餐

如果我们想减少未命中,为什么不让缓存更“灵活”一些呢?在一个简单的​​直接映射​​缓存中,每个内存地址只能存储在缓存中的一个特定位置。如果两个频繁使用的地址恰好映射到同一个位置,它们将不断地相互驱逐,即使缓存的其余部分是空的,也会导致“冲突未命中”。

为了解决这个问题,我们可以增加​​相联度​​。一个​​四路组相联​​缓存允许一个内存地址存储在一个组内的四个可能位置中的任何一个。一个​​全相联​​缓存是最灵活的:任何地址都可以存储在任何地方。

更高的相联度减少了冲突未命中。但这是有代价的。要在直接映射缓存中找到数据,硬件只需检查一个位置。要在四路组相联缓存中找到它,硬件必须同时检查四个位置。在全相联缓存中,它必须检查每一个条目。这种用于比较的额外硬件使缓存更复杂,消耗更多功率,并且至关重要地,增加了命中时间。

这就带来了一个经典的工程权衡。我们是接受更高的未命中率(mmm)以换取更低的命中时间(ThitT_{hit}Thit​),还是增加命中时间以降低未命中率?最优选择取决于未命中惩罚。如果访问主存非常缓慢,那么为了避免那些灾难性的未命中,为每次命中支付一点小的额外代价可能是值得的。目标始终是最小化整体的 AMAT,而不是任何单个组成部分。

地址簿:转换虚拟现实

到目前为止,我们一直生活在一个简单的物理内存地址世界里。但是现代操作系统为每个程序创造了一个美丽的幻象:它拥有自己私有的、从地址零开始的连续内存空间。这就是​​虚拟内存​​。当 CPU 生成一个虚拟地址时,它必须被翻译成一个物理地址,然后才能访问缓存或主存。

这个翻译过程可能很慢。它涉及从一个名为​​页表​​的数据结构中读取,而页表本身就存储在主存中。一个多级页表可能需要多次内存访问才能找到实际数据在哪里!这会摧毁我们的性能。

为了避免这种情况,计算机使用了另一个缓存:​​转译后备缓冲器 (TLB)​​。TLB 是一个小型、快速的缓存,存储最近使用过的虚拟到物理地址的翻译。它是我们内存系统的地址簿。

在每次内存访问之前,硬件首先检查 TLB。

  • ​​TLB 命中:​​ 翻译瞬间完成。
  • ​​TLB 未命中:​​ 硬件必须执行一次缓慢的“页表遍历”,从主存中的页表读取以找到翻译。

注意到这个模式了吗?其逻辑与数据缓存完全相同。​​有效内存访问时间 (EMAT)​​ 现在包括了地址翻译的时间。在翻译和数据访问顺序发生的最简单情况下,一次内存引用的总时间是翻译时间和数据访问时间之和。使用期望定律,EMAT 是:

EMAT=(平均翻译时间)+(平均数据访问时间)\mathrm{EMAT} = (\text{平均翻译时间}) + (\text{平均数据访问时间})EMAT=(平均翻译时间)+(平均数据访问时间)

平均翻译时间本身由 TLB 的性能决定:Ttrans,avg=TTLB_hit+mTLB⋅Tpage_walk_penaltyT_{trans, avg} = T_{TLB\_hit} + m_{TLB} \cdot T_{page\_walk\_penalty}Ttrans,avg​=TTLB_hit​+mTLB​⋅Tpage_walk_penalty​。我们再次看到了使用专门的缓存来加速对更大、更慢数据结构的访问的原理。就像数据缓存一样,TLB 也可以构建成层次结构(L1 TLB, L2 TLB)以进一步减少未命中的惩罚。在最佳情况下,当一个程序长时间在单个页面内处理数据时,几乎每次访问都会是 TLB 命中,翻译开销变得微乎其微。

一次内存请求的完整旅程

让我们追踪一次内存请求的完整、英勇的旅程。

  1. ​​地址翻译:​​ CPU 发出一个虚拟地址。内存管理单元首先在 TLB 中查找。

    • 如果是 TLB 命中: 太棒了!物理地址在大约一纳秒内可知。
    • 如果是 TLB 未命中: 噢哦。硬件开始进行页表遍历,这涉及多次对主存的缓慢访问。这可能需要数百纳秒。
  2. ​​数据访问:​​ 现在,拿到了物理地址,旅程继续前往数据缓存层次结构。

    • L1 缓存检查: 数据是否在 L1 缓存中?如果是(L1 命中),数据被返回给 CPU。旅程结束。总时间:也许几纳秒。
    • L2 缓存检查: 如果是 L1 未命中,系统检查 L2 缓存。如果是 L2 命中,数据被返回,可能需要 10-20 纳秒。
    • 主存访问: 如果 L2 也未命中,我们最终踏上前往主存仓库的漫长旅程。这可能需要超过一百纳秒。

请求的最终时间是阶段 1 和阶段 2 所花费时间的总和。EMAT 是所有这些可能路径的宏大平均值,是为隐藏延迟而构建的层层优化的证明。

窥探库房:DRAM 行缓冲区

我们的模型还可以进一步细化。主存本身并不是一个简单的整体。DRAM 由存储体 (bank) 和单元阵列组成。访问一个内存单元需要首先激活一整“行”数千个比特,并将其加载到一个​​行缓冲区​​中,这实际上是 DRAM 芯片上的一个小缓存。如果下一次访问是同一行(​​行缓冲命中​​),那么可以非常迅速地从这个缓冲区提供服务。如果访问的是不同行(​​行缓冲冲突​​),则当前行必须被写回,然后激活新行,这个过程要慢得多。这表明,局部性和缓存的原理一直延伸到内存芯片本身的物理操作。

当局部性失效时:颠簸的危害

如果一个程序的访问模式没有局部性会发生什么?如果它主动地与缓存作对呢?想象一个程序,在每次访问之后,都跳转到其内存的一个完全不同的部分,然后又跳回来,在两个无法同时放入 TLB 的大工作集之间交替。

这会造成一种称为​​颠簸 (thrashing)​​ 的灾难性情况。每次对工作集 A 的访问都会从 TLB 中驱逐一个对工作集 B 可能有用的翻译。当程序接着访问 B 时,就会导致 TLB 未命中,从而引入 B 的翻译并驱逐 A 的一个翻译。TLB 命中率骤降至接近零。处理器几乎所有的时间都不是在计算,而是在等待缓慢的页表遍历。

这是一个深刻的教训:性能不仅仅是硬件的一个特性。它是一种伙伴关系。世界上最复杂的内存层次结构也无法拯救一个没有局部性的程序。缓存和层次结构的优美而复杂的舞蹈是建立在程序行为具有某种可预测性——一种时间和空间的节奏——的假设之上的。当这种节奏被打破时,音乐便停止了。

应用与跨学科联系

在探讨了内存层次结构的原理之后,我们现在来到了旅程中最激动人心的部分。我们将看到,有效内存访问时间 (EMAT) 的简单而优雅的公式,并不仅仅是一种学术操练,它是一个强大的透镜,通过它我们可以理解、设计和优化广阔而复杂的现代计算世界。就像一把万能钥匙,它能解锁从处理器硅片到全球云架构的各种洞见。

架构师的平衡之术

想象你是一位架构师,不是设计建筑,而是设计微芯片。你面临一个根本性的选择。你可以设计一个命中时快如闪电的缓存内存,一个能在心跳之间返回数据的微型飞毛腿。或者,你可以设计一个更大、更复杂的缓存,它在命中时稍慢,但在预测哪些数据将被需要方面做得更好,从而实现更低的未命中率。你该如何选择?

这不是一个哲学问题;这是一个可以用量化答案解决的具体工程问题。EMAT 就是解决这个问题的工具。假设缓存 1 的命中时间为 thit,1t_{\text{hit},1}thit,1​,未命中率为 m1m_1m1​,而缓存 2 的命中时间较高 thit,2t_{\text{hit},2}thit,2​,但未命中率较低 m2m_2m2​。未命中的总时间惩罚 tmisst_{\text{miss}}tmiss​ 对两者是相同的。选择完全取决于工作负载。对于一个工作集很小、几乎完全能装入缓存的程序,未命中率 mmm 将接近于零。在这种情况下,EMAT,thit+m⋅tmisst_{\text{hit}} + m \cdot t_{\text{miss}}thit​+m⋅tmiss​,主要由命中时间决定,命中更快的缓存 1 是明显的赢家。然而,对于一个局部性差、导致频繁未命中的大型应用,第二项 m⋅tmissm \cdot t_{\text{miss}}m⋅tmiss​ 变得至关重要。此时,缓存 2 较低的未命中率将绰绰有余地补偿其较慢的命中时间,从而产生更好的整体 EMAT。因此,架构师的决策并非孤立地构建“最好”的缓存,而是为预期的“流量”——它将要运行的应用程序——构建合适的缓存。这个根本性的权衡是我们 EMAT 框架的第一个优美应用。

软硬件的精妙舞蹈

处理器不是一个独奏者;它与运行的软件共同演奏一曲二重奏。最终的性能是它们错综复杂的互动结果。EMAT 让我们能够为这场舞蹈编排,以达到最高效率。

考虑两个大矩阵相乘的经典问题。一个朴素的实现可能会按简单的模式遍历行和列,但这可能非常低效。高性能软件则使用“分块”方法,将矩阵分解成小方块,然后对这些块进行乘法运算。但是块应该多大呢?太小,循环控制的开销很高。太大,我们又会遇到另一个问题。关键在于观察转译后备缓冲器 (TLB),这个特殊的缓存存储了最近从虚拟页地址到物理页地址的翻译。如果在内层核心中涉及的三个块的组合数据足迹超过了 TLB 所能追踪的页数,我们将会遭遇 TLB 未命中的风暴。每次未命中都会强制在主存中进行一次缓慢的页表遍历。通过使用 EMAT,我们可以将 TLB 命中率建模为块大小 BBB 的函数。然后,我们可以选择使我们的工作集页面舒适地保持在 TLB 内部的最大的 BBB,从而确保接近完美的命中率,并最小化地址翻译所花费的时间。在这里,EMAT 指导程序员根据硬件的节奏来调整他们的算法。

硬件的节奏在更简单的模式中也能感受到。想象一下扫描一个非常大的数据数组。你可能决定访问每个元素(步长为 1),或者你可能只需要采样,访问每第 16 个元素(步长为 16)。这个选择如何影响性能?每次你的步长跨越一个页面边界,你就有可能发生 TLB 未命中。如果你的步长相对于页面大小很小,比如说在 4096 字节的页面上以 512 字节的步长访问 8 字节的元素,那么在第一次对该页面的未命中之后,你将“免费”获得 4096512=8\frac{4096}{512} = 85124096​=8 次访问。你的未命中率是 18\frac{1}{8}81​。那么每次访问的平均时间就是基础内存时间加上分摊的页表遍历成本:tmem+18⋅(page walk cost)t_{\text{mem}} + \frac{1}{8} \cdot (\text{page walk cost})tmem​+81​⋅(page walk cost)。EMAT 以数学的清晰度揭示了应用程序的访问模式如何直接转化为性能。

这种舞蹈甚至延伸到指令本身。如果我们能让我们的程序变得更小呢?代码压缩技术可以减少程序的动态足迹。这意味着取指 CPU 执行命令的指令缓存,可以一次性容纳更多的程序逻辑。结果呢?更低的指令缓存未命中率。这直接降低了指令获取的 EMAT,进而降低了整体的每指令周期数 (CPI),使整个程序运行得更快。这是一个优美的例证,说明内存层次结构的性能对于获取“食谱”(代码)与访问“食材”(数据)同样至关重要。

并行与规模的复杂性

在现代多核处理器的世界里,我们不再只有一个舞者。我们有一整个舞团同时在舞台上,他们最好不要互相妨碍。EMAT 帮助我们理解并行执行中那些微妙且常常出人意料的交通拥堵。

其中最著名的一个是“伪共享”。想象两个线程在两个不同的核心上运行。线程 A 正在更新变量 x,线程 B 正在更新变量 y。逻辑上,它们是独立的。但如果 x 和 y 恰好在内存中相邻,近到它们落在同一个 64 字节的缓存块内呢?一连串的悲剧随之而来。当线程 A 写入 x 时,它的核心必须获得该缓存块的独占所有权,从而使线程 B 缓存中的副本失效。片刻之后,当线程 B 写入 y 时,它的核心必须使线程 A 的副本失效,并为自己取回该块。任一线程的每一次写入都会导致一次一致性未命中,迫使缓存块在核心之间进行缓慢而昂贵的传输。这些线程虽然处理的是不同的数据,却陷入了一场“乒乓”比赛,来回抢夺缓存块。在这种灾难性的情况下,这些更新的未命中率变为 1,每次访问的额外时间就是完整、未经缓解的一致性未命中惩罚。EMAT 量化了这场性能灾难,指导并行程序员谨慎地组织他们的数据,确保独立的数据存在于独立的缓存块中。

进一步扩展,我们遇到了具有非一致性内存访问 (NUMA) 的系统,这在服务器和超级计算机中很常见。在 NUMA 机器中,处理器访问连接到其自身插槽的内存(本地内存)比访问连接到另一个处理器插槽的内存(远程内存)要快得多。我们 EMAT 方程中的“未命中惩罚”不再是一个单一的数字;它是一个变量。缓存未命中时的 AMAT 变成一个加权平均值:AMAT=p⋅tlocal+(1−p)⋅tremoteAMAT = p \cdot t_{\text{local}} + (1-p) \cdot t_{\text{remote}}AMAT=p⋅tlocal​+(1−p)⋅tremote​,其中 ppp 是访问是本地的概率。在这种机器中,操作系统必须成为一个聪明的编舞者。通过监控哪些线程正在访问哪些内存页面,它可以将页面从远程内存迁移到本地内存,增加概率 ppp,并显著降低系统的整体 AMAT。

揭开幕后:虚拟化与专业化

EMAT 框架对于分析支撑现代计算的一些最复杂技术(如虚拟化)是不可或缺的。如何能够在没有严重性能损失的情况下,在“虚拟机”内运行一个完整的客户机操作系统?答案在于与内存层次结构的深度互动。

在具有硬件辅助虚拟化的系统中,将客户机的虚拟地址转换为真实机器的物理地址是一个两阶段的问题。在 TLB 未命中时,硬件必须首先遍历客户机的页表以找到客户机物理地址。然而,这些客户机页表的地址本身是客户机物理地址,因此也必须通过遍历由虚拟机监控程序(Hypervisor)管理的第二套页表(通常称为扩展页表 (EPT) 或嵌套页表 (NPT))来进行翻译。因此,一次 TLB 未命中可能会引发一连串的内存访问——在一个有四级页表的系统中,一次地址翻译可能需要超过 20 次内存访问!EMAT 让我们能够计算这对性能造成的毁灭性影响,并理解为什么像影子页表这样预先计算直接映射的替代技术会被开发出来。

根据工作负载定制系统的原则也催生了专门的硬件。图形处理器 (GPU) 是专业化的奇迹。在渲染图像时,线程通常以二维块的形式访问像素。这种二维空间局部性很难被标准的、基于行的 L1 缓存所捕获。因此,GPU 包含一个专门的​​纹理缓存​​。这个缓存被设计用来理解二维局部性,使用巧妙的寻址和过滤来最大化重用。对于具有高二维重用的工作负载,纹理缓存可以实现非常低的有效未命中率。通过比较纹理缓存路径的 AMAT 和通用 L1 缓存路径的 AMAT,我们可以量化这种硬件专业化所带来的巨大加速。

超越速度:能量的通用货币

也许最深刻的洞见是,EMAT 的逻辑结构——不同结果成本的加权平均——并不仅限于时间。它是一种推理预期成本的通用方法。

考虑一个靠小电池运行的物联网 (IoT) 设备。对于这个设备来说,能量和时间一样宝贵。每次内存访问都有纳秒和纳焦耳的成本。一次缓存命中消耗少量能量 EhE_hEh​。一次缓存未命中则消耗得多得多,Eh+EmE_h + E_mEh​+Em​,因为设备必须启动其主内存接口。我们可以用一个相同的公式来定义“每次访问的平均能量”:Eavg=Eh+MR⋅EmE_{\text{avg}} = E_h + MR \cdot E_mEavg​=Eh​+MR⋅Em​。

一个物联网设备可能需要同时满足两个约束:它的平均响应时间必须低于某个阈值才能有用,并且它的每次访问平均能量必须低于另一个阈值以确保合理的电池寿命。通过计算时间约束所允许的最大未命中率 (MRMRMR) 和能量约束所允许的最大 MRMRMR,系统设计者可以找到两者中更严格的一个。这决定了设备软件真正的操作限制。这最后一个应用展示了这个概念真正的美和统一性:指导价值数十亿美元的超级计算机设计的同一个简单原则,也指导着微小的、电池供电的传感器的设计。它证明了科学和工程中基本思想的力量。