目录
5.3.2 写通过(Write-through)或写回(Write-back)缓存
5.3.3 读分配(Read-allocate)或写分配(Write-allocate)缓存
5.4 可缓存性和可缓冲性 (Cachability and bufferability)
5.1关于缓存和写缓冲区
缓存和写缓冲区可以用于ARM内存系统中以提高它们的平均性能。
缓存(Cache): 缓存是一块高速内存位置,其地址可以更改,目的是提高内存访问的平均速度。缓存中的每个内存位置被称为缓存行(cache line)。 通常,缓存行地址的更改是自动进行的。每当处理器从内存地址加载数据,而当前没有缓存行持有该数据时,就会为该地址分配一个缓存行,并将数据读入缓存行。如果在缓存行被重新分配给另一个地址之前,再次访问同一地址的数据,缓存可以高速处理内存访问。因此,缓存通常加快了对数据的第二次及后续访问。实际上,这些第二次及后续访问足够常见,以至于可以产生显著的性能提升。这种效应被称为时间局部性(temporal locality)。
为了减少存储缓存行当前地址的百分比开销,每个缓存行通常由多个内存字组成。这增加了对缓存行的第一次访问的成本,因为需要从主内存加载多个字以满足对一个字的请求。然而,这也意味着对同一缓存行中另一个字的后续访问可以由缓存高速处理。这种访问也足够常见,足以显著提高性能。这种效应被称为空间局部性(spatial locality)。
由于它所寻址的数据已经在缓存中,因此可以高速处理的内存访问被称为缓存命中(cache hit)。其他内存访问被称为缓存未命中(cache miss)。
写缓冲区(Write Buffer): 写缓冲区是一块高速内存,其目的是优化对主内存的存储。当发生存储操作时,其数据、地址和其他细节(如数据大小)以高速写入写缓冲区。然后写缓冲区以主内存速度(通常比ARM处理器的速度慢得多)完成存储操作。与此同时,ARM处理器可以继续以全速执行进一步的指令。
写缓冲区和缓存引入了一些潜在问题,主要是由于:
- 内存访问发生在程序员通常预期的时间之外。
- 有多个物理位置可以保存数据项。
本章讨论了这些问题,并描述了可以用来解决这些问题的缓存和写缓冲区控制设施。
注意: 本章中描述的缓存是使用内存访问的虚拟地址访问的。这意味着当虚拟到物理地址映射发生变化或在某些其他情况下,它们需要被失效和/或清理,如B5-10页的“内存一致性”中所述。
如果使用了快速上下文切换扩展(FCSE),则本章中对虚拟地址的所有引用都意味着它生成的修改后的虚拟地址。
5.2 Cache 组织
缓存中的基本存储单元是缓存行(cache line)。当缓存行包含缓存数据或指令时,它被认为是有效的;当它不包含时,则是无效的。在重置时,所有缓存行在缓存中被无效化。当数据或指令从内存加载到缓存行时,缓存行变得有效。
当缓存行有效时,它包含一系列连续主内存位置的最新值。这个块的长度(因此也是缓存行的长度)始终是2的幂,通常为16字节(4个字)或32字节(8个字)。如果缓存行长度是2^L字节,主内存位置的块总是2^L字节对齐的。这样的主内存位置块被称为内存缓存行或简称缓存行。
由于这种对齐要求,虚拟地址的位[31:L]对于缓存行中的所有字节都是相同的。 当ARM处理器提供的虚拟地址的位[31:L]与某个有效缓存行的虚拟地址的相同位匹配时,就会发生缓存命中。
为了简化和加速确定是否发生缓存命中的过程,缓存通常被划分为多个缓存集(cache sets)。缓存集的数量始终是2的幂。如果缓存行长度是2^L字节,并且有2^S个缓存集,ARM处理器提供的虚拟地址的位[L+S-1:L]用于选择一个缓存集。只有该集中的缓存行才允许持有该地址处的数据或指令。
虚拟地址的剩余位(位[31:L+S])被称为它的标签位。如果ARM处理器提供的虚拟地址的标签位与选定缓存集中某个有效行的标签位匹配,则发生缓存命中。
5.2.1 集联性(Set-associativity)
缓存的集联性指的是其每个缓存集中的缓存行数量。这个数字可以是任何大于或等于1的数,并不局限于是2的幂。
低集联性通常简化了缓存查找。然而,如果使用特定缓存集的频繁使用的内存缓存行数量超过了集联性,主内存活动就会增加,性能就会下降。这被称为缓存争用(cache contention),并且随着集联性的减少而更有可能发生。
两个极端情况是全相联缓存和直接映射缓存:
- 全相联缓存只有一个缓存集,它由整个缓存组成。它是N路集联的,其中N是缓存中缓存行的总数。在全相联缓存中,任何缓存查找都需要检查每个缓存行。
- 直接映射缓存是一种一对一路集联的缓存。每个缓存集由单个缓存行组成,因此缓存查找只需要选择并检查一个缓存行。然而,在直接映射缓存中,缓存争用特别容易发生。
在每个缓存集中,缓存行从0编号到(集联性-1)。与每个缓存行相关联的数字称为其索引。一些缓存操作接受缓存行索引作为参数,允许软件循环系统地遍历一个缓存集。
5.2.2 缓存大小
通常,随着缓存大小的增加,更高百分比的内存访问是缓存命中。这减少了每次内存访问的平均时间,从而提高了性能。然而,大缓存通常使用大量的硅片面积和功率。因此,在ARM内存系统中可以使用不同大小的缓存,这取决于性能、硅片面积和功耗的相对重要性。
缓存大小可以分解为三个因素的乘积:
- 缓存行长度LINELEN,以字节为单位。
- 集联性ASSOCIATIVITY。一个缓存集由ASSOCIATIVITY个缓存行组成,因此缓存集的大小是ASSOCIATIVITY × LINELEN。
- 组成缓存的缓存集数量NSETS。
如果使用单独的数据和指令缓存,可以为每个缓存使用不同的参数值,并且得到的缓存大小可以不同。
如果系统控制协处理器支持缓存类型寄存器,它可以用来确定这些缓存大小参数。
5.3 缓存类型
缓存的可能类型很多,可以通过以下实现选择来区分:
- 它们的大小
- 它们如何处理指令获取
- 它们如何处理数据写入
- 缓存中有多少部分有资格保存特定数据项
以下小节详细描述了这些实现选择中的一些。有关这些选择的详细信息,也可以参见的“缓存类型寄存器”,了解如何为包含缓存类型寄存器的实现确定这些选择。
注意: 高性能内存系统可以包含多个级别的缓存,第一级小而非常高速,下一级更大且稍慢,以此类推,直到主内存,主内存是内存系统中最大且最慢的组成部分。此外,不同的缓存级别可以是不同类型的。
本章仅描述第一级(或唯一级别)的缓存,缓存类型寄存器也是如此。如果内存系统实现提供了控制第二级或更高级别缓存的设施,这些设施的细节是实现定义的(IMPLEMENTATION DEFINED)。
因此,本章其余部分对主内存的所有引用都指的是第一级缓存之外的所有内存系统,包括任何进一步的缓存级别。
5.3.1 统一缓存或分离缓存
内存系统在处理指令获取时可以使用与处理数据加载和存储相同的缓存。这种缓存被称为统一缓存。
或者,内存系统在处理指令获取时可以使用与处理数据加载和存储不同的缓存。在这种情况下,这两个缓存分别被称为指令缓存和数据缓存,统称为分离缓存。
使用分离缓存的优点是,内存系统通常可以在同一个时钟周期内处理指令获取和数据加载/存储,而不需要缓存内存是多端口的。
主要的缺点是必须小心避免由于指令缓存与数据缓存和/或主内存相比变得过时而引起的问题。
内存系统也可能只有指令缓存而没有数据缓存,或者反之。对于内存系统架构而言,这样的系统被视为具有分离缓存,其中一个缓存不存在或大小为零。
5.3.2 写通过(Write-through)或写回(Write-back)缓存
当数据存储访问发生缓存命中时,包含数据的缓存行会被更新以包含其新值。由于这个缓存行最终会被重新分配给另一个地址,数据的主内存位置也需要被写入新值。处理这一点有两种常见的技术:
- 在写通过缓存中,新数据也会立即被写入主内存位置。(这通常是通过写缓冲区完成的,以避免降低处理器的速度。)
- 在写回缓存中,缓存行被标记为脏的,这意味着它包含的数据值比主内存中的更更新。每当一个脏缓存行被选中重新分配给另一个地址时,当前在缓存行中的数据会被写回主内存。以这种方式回写缓存行中的内容被称为清理缓存行。写回缓存的另一个常用术语是写回缓存(copy-back cache)。
写通过缓存的主要缺点是,如果处理器的速度相对于主内存的速度足够快,它生成数据存储的速度比写缓冲区处理的速度更快。结果是,处理器因必须等待写缓冲区能够接受更多数据而被减速。
因为写回缓存仅在缓存行重新分配时将数据写入主内存一次,即使对缓存行发生了多次存储,写回缓存通常比写通过缓存生成更少的主内存存储。这有助于缓解写通过缓存中描述的问题。然而,写回缓存也有一些缺点,包括:
- 缓存和主内存内容之间的差异持续时间更长
- 在数据加载可以完成之前,主内存操作的最坏情况序列更长,这可能会增加系统的最坏情况中断延迟
- 实现的复杂性增加
一些写回缓存允许在写回和写通过行为之间进行选择
5.3.3 读分配(Read-allocate)或写分配(Write-allocate)缓存
处理数据存储访问时的缓存未命中有两种常见技术:
- 在读分配缓存中,数据简单地被存储到主内存中。只有在数据被读/加载时,缓存行才会被分配给内存位置,而不是在被写/存储时。
- 在写分配缓存中,缓存行被分配给数据,主内存的当前内容被读入其中,然后数据被写入缓存行。(它也可以被写入主内存,这取决于缓存是写通过还是写回。)
这些技术的主要优点和缺点与性能相关。与读分配缓存相比,写分配缓存可能会产生额外的主内存读取访问,这些访问在其他情况下可能不会发生,或者因为数据现在在缓存中,从而节省了后续存储操作的主内存访问。这些之间的平衡主要取决于对相关数据的加载/存储访问的数量和类型,以及缓存是写通过还是写回。
在ARM内存系统中使用写分配还是读分配缓存是实现定义的(IMPLEMENTATION DEFINED)。这意味着具体的缓存策略可能会根据特定的ARM实现和设计需求而有所不同。设计人员需要根据预期的工作负载和性能要求来决定哪种类型的缓存更适合他们的系统。例如,如果系统预期会有大量的写操作,并且这些写操作会频繁地重用相同的数据,那么写分配缓存可能会提供更好的性能,因为它可以减少对主内存的访问次数。相反,如果系统的读取操作远多于写操作,或者写操作不太频繁,那么读分配缓存可能是一个更合适的选择。
5.3.4 替换策略
如果缓存不是直接映射的,那么对于一个内存地址的缓存未命中需要在与该地址相关的缓存集中重新分配一个缓存行。选择这个缓存行的方式被称为缓存的替换策略。
两种典型的替换策略是:
随机替换(Random replacement): 缓存控制逻辑包含一个伪随机数生成器,其输出用于选择要重新分配的缓存行。
轮询替换(Round-robin replacement): 缓存控制逻辑包含一个计数器,用于选择要重新分配的缓存行。每次执行此操作时,计数器都会增加,以便下次做出不同的选择。
一些缓存允许选择使用的替换策略。通常,一个选择是一个简单、易于预测的策略,如轮询替换,这允许相对容易地确定代码序列的最坏情况缓存性能。这类策略的主要缺点是它们的平均性能可能在程序的相对较小细节变化时突然变化。
例如,假设一个程序正在循环地访问数据项D1、D2、...、Dn,并且所有这些数据项恰好使用相同的缓存集。在m路集联缓存中使用轮询替换,程序可能会遇到:
- 当n ≤ m时,这些数据项的缓存命中率接近100%。
- 一旦n变为m+1或更大,缓存命中率立即降为0%。
换句话说,处理的数据量轻微增加可能导致缓存的效果发生重大变化。
当缓存允许选择替换策略时,第二个选择通常是随机替换等行为不太容易预测的策略。这使得最坏情况的行为更难以确定,但也使得缓存的平均性能随着工作集大小等参数的变化更加平滑。
5.4 可缓存性和可缓冲性 (Cachability and bufferability)
由于缓存和写缓冲区改变了对主内存的访问数量、类型和时机,它们不适用于某些类型的内存位置。特别是,缓存依赖于正常的内存特性,例如:
- 从内存位置加载返回该位置存储的最后一个值,无副作用。
- 存储到内存位置除了改变内存位置值之外无其他副作用。
- 从同一内存位置连续进行两次加载都会得到相同的值。
- 连续两次存储到同一内存位置会导致其值变为第二次存储的值,第一次存储的值被丢弃。
内存映射的I/O位置通常缺乏这些特性之一或多个,因此不适合缓存。
此外,写缓冲区和写回缓存依赖于将存储操作延迟到主内存,以便实际存储操作发生在ARM处理器执行存储指令之后。同样,这对于内存映射的I/O位置可能不适用。
一个典型的例子是ARM中断处理程序,它存储到I/O设备以确认它正在生成的中断,然后重新启用中断(无论是显式地还是作为从中断处理程序返回时执行的SPSR → CPSR传输的结果)。
- 如果写缓冲区或写回缓存延迟了对I/O设备的写操作,那么在处理器看来,写操作已经完成(因为数据已经写入缓冲区或缓存),但实际上I/O设备可能还没有收到这个数据。
- 在上述例子中,如果中断处理程序写入I/O设备以确认中断,并且这个写操作被延迟了,那么当处理器重新启用中断时,I/O设备可能仍然认为中断没有被处理,因为它还没有收到确认信号。这可能导致额外的、不必要的中断处理程序调用,因为设备会再次发出中断信号。
如果实际存储到I/O设备的操作发生在ARM存储指令执行时,那么在中断被重新启用时,I/O设备不再请求中断。但如果写缓冲区或写回缓存延迟了存储,I/O设备可能仍在请求中断。如果是这样,这将导致中断处理程序出现一个错误的额外调用。
由于存在这些问题,内存管理单元和保护单元架构都允许将内存区域指定为不可缓存、不可缓冲或两者兼有。这是通过使用内存地址为每次内存访问生成两个位(C和B)来完成的。
将内存区域设置为不可缓冲的目的是为了防止对它的存储操作被延迟。然而,如果该区域是可缓存的,并且正在使用写回缓存,存储操作仍然可能被延迟。这意味着对于写回缓存来说,将C == 1, B == 0解释为缓存/未缓冲并不适用。因此,这种解释仅在写通过缓存中有效。在写回缓存中,它反而会导致不可预测的行为,或者选择写通过缓存,如上的表所示。
注意:
- 内存映射的I/O位置通常需要被标记为不可缓存的原因,实际上是为了防止内存系统硬件错误地优化掉对该位置的加载和存储操作。如果I/O系统是用高级语言编程的,这还不够。编译器也需要被告知不要优化掉这些加载和存储操作。在C语言和相关语言中,通过在内存映射的I/O位置的声明中使用volatile限定符来实现这一点。
- 出于性能原因,有时也可能希望将内存区域标记为不可缓存。这通常发生在频繁使用的大数组上,但其访问模式包含的时间或空间局部性很小。将这样的数组设置为不可缓存可以避免在通常只发生一次访问时加载整个缓存行的成本。同时,这也意味着其他数据项从缓存中被替换的频率更低,这提高了缓存对其他数据的有效性。
5.5 内存一致性
当使用缓存和/或写缓冲区时,系统可能会保存内存位置的多个版本。这些值的可能物理位置包括主内存、写缓冲区和缓存。如果使用了单独的缓存,指令缓存和数据缓存可能都包含某个内存位置的值。
并非所有这些物理位置必然包含最近写入该内存位置的值。内存一致性问题就是要确保在读取某个内存位置时(无论是数据读取还是指令获取),实际获得的值总是最近写入该位置的值。
在ARM内存系统架构中,有些内存系统一致性方面需要系统自动提供。其他方面则通过内存一致性规则来处理,这些规则限定了程序的行为,以维持内存一致性。违反内存一致性规则的程序行为是不可预测的(UNPREDICTABLE)。
接下来的小节将更详细地讨论内存一致性的特定方面:
- 地址映射变化
- 指令缓存一致性
- 直接内存访问(DMA)操作
- 其他内存一致性问题
5.5.1 地址映射变化
在实现虚拟到物理地址映射的ARM内存系统中(基于MMU的内存系统),对于缓存行关联的地址有两种实现选择:
- 它可以是缓存行中数据的虚拟地址。这是更常见的选择,因为它允许缓存行查找与地址转换并行进行。
- 它可以是缓存行中数据的物理地址。
如果一个实现设计为使用虚拟地址,虚拟到物理地址映射的变化可能会导致主要的内存一致性问题,因为任何在重映射地址范围内的缓存中的数据都不再与正确的物理内存位置关联。
同样,写缓冲区中的数据可以与虚拟地址或物理地址关联,这取决于是在数据放入写缓冲区时还是在从写缓冲区存储到主内存时进行地址映射。如果写缓冲区设计为使用虚拟地址,虚拟到物理地址映射的变化可能会再次导致内存一致性问题。
这些问题可以通过在虚拟到物理地址映射变化之前执行实现定义的缓存和/或写缓冲区操作序列来避免。通常,这个序列包含一个或多个以下操作:
- 如果是写回缓存,则清理数据缓存
- 使数据缓存失效
- 使指令缓存失效
- 排空写缓冲区。
对于执行地址映射变化的代码及其访问的任何数据,可能还需要将其标记为不可缓存、不可缓冲或两者兼有。
5.5.2 指令缓存一致性
内存系统可以从单独的指令缓存中满足指令获取请求。指令缓存行获取可以从主内存中满足,并且没有要求数据存储更新单独的指令缓存。这意味着以下事件序列可能会导致潜在的内存一致性问题:
- 从地址A1获取指令,导致包含该地址的缓存行被加载到指令缓存中。
- 发生数据存储到与A1相同缓存行的地址A2,导致数据缓存、写缓冲区和主内存的一个或多个更新,但未更新指令缓存。(A2可能是与A1相同的地址,也可能是同一缓存行中的不同地址。两种情况下的考虑相同。)
- 从地址A2执行指令。这可能导致执行内存位置的旧内容或新内容,取决于缓存行是否仍在指令缓存中或需要重新加载。
这个问题可以通过在步骤2和3之间执行实现定义的缓存控制操作序列来避免。通常,这个序列包括:
- 对于具有统一缓存的实现,什么也不做
- 对于具有分离缓存和写通过数据缓存的实现,使指令缓存失效
- 对于具有分离缓存和写回数据缓存的实现,先清理数据缓存,然后使指令缓存失效。
因此,维持指令缓存一致性的内存一致性规则是:如果数据存储将指令写入内存,则在执行指令之前必须执行实现定义的序列。需要执行此操作的典型情况是当可执行文件加载到内存中时。在加载文件并在分支到新加载代码的入口点之前,必须执行实现定义的序列,以确保新加载的程序正确执行。
当这种情况发生时,所需的缓存清理和失效的性能成本可能很大,这既是直接执行缓存控制操作的结果,也是间接因为指令缓存需要重新加载的结果。这意味着涉及频繁实时生成和执行小代码片段的编程技术必须小心处理,因为将它们移植到新的内存系统可能需要仔细重新设计和优化,以避免过度的性能损失。
注意:
- 维持指令缓存一致性所需的序列是指令内存屏障(IMB)执行的序列的一部分,但不一定是全部。有关更多详细信息,请参见“指令内存屏障(IMB)”。
- 在某些实现中,可以利用对新存储指令所占地址范围的了解来减少所需的缓存操作成本。例如,可能可以将缓存清理和失效限制在该地址范围内。是否可以这样做是实现定义的。
- 如果已知包含新存储指令的地址范围都不在指令缓存中,则上述描述的内存一致性问题不会发生。然而,由于以下原因,很难在所有ARM实现中都确定这一点:
- 缓存行中的任何指令获取都会导致该缓存行中的所有指令被加载到指令缓存中。
- 通常,一些指令被获取但从未执行,所以可能已经加载了指令缓存行但不包含任何已执行的指令。此外,尽管获取但未执行的指令通常接近已执行的指令,但在使用分支预测或类似技术的实现中,情况并非如此。
- 因此,使用这种技术来避免指令缓存一致性问题的代码不是完全实现独立的。
5.5.3 直接内存访问(DMA)操作
I/O设备可以执行直接内存访问(DMA)操作,直接访问主内存,而不需要处理器执行任何相关数据访问。
如果DMA操作在不更新缓存和/或写缓冲区的情况下存储到主内存,可能会违反通常依赖的规则,从而简化内存一致性问题。例如,通常的情况是如果数据项在缓存中,那么它在主内存中的副本不会比缓存中的副本更新。这允许在不明确检查主内存中是否有更近期写入的版本的情况下,返回缓存中的值进行数据加载。然而,DMA存储到主内存可能会导致主内存值比缓存值更近期写入。
同样,如果DMA操作从主内存加载数据而不检查缓存和/或写缓冲区以查看它们是否包含更近期的版本,它可能会获得数据的过时版本。
在这两种情况下,一个可能的解决方案是让DMA也访问缓存和写缓冲区。然而,这将显著复杂化内存系统。
因此,内存系统实现可以对处理DMA操作有实现定义的内存一致性规则。
通常,这些涉及以下一个或多个操作:
- 将涉及DMA操作的内存区域标记为不可缓存和/或不可缓冲
- 至少对涉及DMA操作的地址范围进行数据缓存的清理和/或失效
- 排空写缓冲区
- 在已知DMA操作完成之前,限制处理器对涉及DMA操作的地址范围的访问。
5.5.4 其他内存一致性问题
上述未涵盖的内存一致性问题涉及数据缓存、主内存和/或写缓冲区,并且不涉及虚拟到物理地址映射的变化或DMA操作。所有此类问题必须由内存系统自动处理,以确保返回给ARM处理器的值是所有可能的物理位置中最新的值。
注意: 这一要求仅适用于单个处理器。如果系统包含多个ARM处理器,则所有涉及不同处理器之间内存一致性的问题都是系统依赖的。