AXI-5.3.1 Memory type & requirements

AxCACHE 信号的组合表示内存类型。表 A5.3 显示了内存类型的编码。 括号中的值是允许的,但不是首选的。表中未显示的值是保留的。

按照分类名称可以看出,内存类型主要分为两大类:Device 和 Normal。
Normal
 类型又进一步细分为 
Non-cacheable

Write-Through
 和 
Write-Back

第一类:Device Memory(设备内存)

这类内存用于访问内存映射外设的寄存器。其特点是访问通常有副作用,且对顺序和精确性要求极高。


0b0000
: Device Non-bufferable

行为:事务不可缓冲不可缓存不可修改

举例:写入一个 UART 的命令寄存器来发送一个字符。这个写入操作必须立即且精确地到达 UART 控制器,不能被延迟或合并。任何缓冲或优化都可能改变硬件行为,导致字符发送失败。


0b0001
: Device Bufferable

行为:事务可缓冲,但不可缓存不可修改

举例:向一个帧缓冲区(Frame Buffer)写入连续的像素数据。虽然帧缓冲区是外设,但允许写入操作在中间缓冲区暂存可以提高系统性能,因为图形渲染对吞吐量要求高,而对单次写入的精确时序要求不那么严格。但事务仍然不能合并或缓存。

第二类:Normal Memory(普通内存)

这类内存用于访问普通的 RAM(如 DDR)。其特点是没有副作用,系统可以对其进行各种优化以提升性能。


0b0010/0b0011
: Normal Non-cacheable

行为:数据不被缓存,但可以缓冲(Bufferable)或不缓冲(Non-bufferable)。

举例共享内存区域。例如,一块被 CPU 和 DMA 控制器共同访问的内存。为了避免缓存一致性问题(即CPU缓存了数据,而DMA直接修改了主内存,导致数据不一致),最简单的方法就是将其标记为 
Non-cacheable
。这样所有访问都直接作用于主内存,保证了所有主设备看到的数据都是一致的。

Write-Through(透写)缓存策略

行为:数据可以被缓存。当 CPU 写入数据时,数据会同时写入缓存和主内存。

举例
0b1110 / 0b1110
: Write-Through Read and Write-Allocate

场景:一段被多个核心共享的代码或数据。

好处:任何核心的写操作都会立即更新主内存,其他核心的缓存可以通过监听总线来使自己的旧副本失效,从而保证一致性。这简化了多核缓存一致性协议,但写操作延迟较高(因为每次都要写主内存)。

Write-Back(回写)缓存策略

行为:数据可以被缓存。当 CPU 写入数据时,数据只写入缓存,并被标记为“脏”。只有当这个缓存行被替换出去时,才写回主内存。

举例
0b1111 / 0b1111
: Write-Back Read and Write-Allocate

场景:CPU 的私有数据或一个核心正在频繁计算的中间结果。

好处:写操作的延迟极低,因为不需要访问慢速的主内存。同时,也减少了总线流量。这是性能最高的缓存模式。

Allocate 属性的细分

在上述缓存策略中,
Allocate
 位(包括 
Other Allocate
)被用来精细控制缓存分配策略。


Write-Through Read-Allocate
 (
0b1110 / 0b0110
)
: 建议只在读未命中时分配缓存行,写未命中时不分配。适用于读多写少的内存。


Write-Back Write-Allocate
 (
0b1011 / 0b1111
)
: 建议只在写未命中时分配缓存行。这是回写模式下的典型行为:当写入一个不在缓存中的数据时,先将整个缓存行从内存读入缓存,然后再修改缓存中的部分数据。

之所以这样分类的根本原因是为了在性能数据一致性硬件正确性 之间实现最优平衡。

保证与外设交互的正确性

设立 
Device
 类型是为了安全。它通过强制 
Non-modifiable
 等属性,确保了对外设寄存器的每一次访问都是精确的,防止了可能改变硬件行为的优化。这是系统稳定性的基石。

最大化内存子系统性能

设立 
Normal
 类型及其子类是为了性能。它解锁了缓存、写缓冲区等所有性能优化技术。

Write-Back vs Write-Through:这是在写延迟/带宽一致性协议复杂度之间的权衡。Write-Back 性能更高,但一致性协议更复杂;Write-Through 一致性更简单,但写性能较差。

精细化管理稀缺的缓存资源


Allocate
 位的细分(No-Allocate, Read-Allocate, Write-Allocate)允许系统根据数据的访问模式来智能地使用缓存。这可以防止缓存污染,确保高频访问的数据能留在缓存中。

简化软件编程模型

通过将内存划分为具有明确定义行为的类型,操作系统和驱动开发者可以轻松地为不同的任务选择合适的内存属性。例如,为DMA缓冲区设置 
Non-cacheable
,为线程堆栈设置 
Write-Back

这些内存类型编码的诞生,是计算机体系结构数十年发展的结晶,直接响应了以下技术演进和需求:

内存映射I/O的普及

早期计算机使用独立的I/O指令和地址空间。而现代处理器(尤其是RISC架构)普遍使用内存映射I/O,即用普通的加载/存储指令访问外设。

带来的问题:总线如何知道一次访问目标是“普通内存”还是“有特殊行为的外设”?

解决方案:定义 
Device
 和 
Normal
 内存类型,从硬件层面区分二者。

缓存层次结构成为标准

缓存是解决处理器-内存速度鸿沟的核心技术。但随着多级缓存和多核的出现,缓存一致性成为巨大挑战。

背景:需要明确的协议来定义缓存行为。

解决方案:AXI 协议通过 
Write-Through
 和 
Write-Back
 类型,将两种最主要的缓存更新策略标准化。这使得不同供应商的IP核能够在一个共享的缓存一致性框架下协同工作。

多核处理器的兴起

多核系统要求对共享数据的访问有严格的定义。


Normal Non-cacheable
 类型提供了一种简单但性能较低的共享方式。


Write-Through
 类型为维护多核一致性提供了一种硬件支持的模式(尽管性能不是最优)。

而现代系统通常使用基于 
Write-Back
 的更复杂的一致性协议(如MESI),但 
AxCACHE
 信号为启动这些协议提供了最初的线索(例如,一个 
Write-Back
 的读请求可能会触发一个“共享”探测)。

对能效和带宽的极致追求


Write-Back
 模式通过减少对主内存的访问次数,直接降低了系统功耗和总线带宽占用。


Allocate
 策略的细分使得系统可以避免不必要的缓存行填充,进一步节约了功耗和带宽。这些优化在移动设备和数据中心等高能效场景下至关重要。

Device Non-bufferable 内存类型的要求

Device Non-bufferable 是 AXI 协议中限制最严格、行为最可预测的内存类型。它被设计用于与那些对访问精确性时序有极高要求的硬件外设进行交互。通过一个具体的场景来理解其所有要求:

场景:CPU 需要配置一个 SPI(串行外设接口)控制器以启动一次数据传输。

首先,CPU 需要向 控制寄存器(地址 
0x4000_1000
)写入 
0x01
 以启用 SPI 主模式。

然后,CPU 需要向 数据寄存器(地址 
0x4000_1004
)写入要发送的数据字节 
0xAB

最后,CPU 需要轮询 状态寄存器(地址 
0x4000_1008
)直到“发送完成”位被置起。

现在,看规则如何应用于此:

1. 写响应必须来自最终目的地

行为:当 CPU 写入控制寄存器后,它必须等待来自 SPI 控制器本身 的写响应(BRESP)。这个响应意味着配置命令已经被 SPI 控制器接收并生效。

举例:如果系统中存在一个写缓冲区,它不能立即返回响应给 CPU。它必须等待 SPI 控制器真正处理了这次写入。这确保了当 CPU 收到响应后,可以确信 SPI 控制器已经准备好接收下一个命令(如写入数据)。

2. 读数据必须来自最终目的地

行为:当 CPU 读取状态寄存器时,它得到的数据必须是直接从 SPI 控制器 读回的实时状态。

举例:任何中间缓存或旧数据副本都是不可接受的。因为状态位(如“发送完成”或“发送缓冲区空”)可能在任何时刻由硬件改变。如果 CPU 读到一个缓存的旧状态,它可能会错误地认为传输尚未完成,或者错误地认为缓冲区已满,从而导致程序逻辑错误。

3. 事务是不可修改的

行为:上述的三个访问(写控制、写数据、读状态)必须保持为三个独立的、原子性的事务。

举例:系统不能将“写控制”和“写数据”这两个写操作合并成一个突发传输。因为 SPI 控制器硬件可能要求先配置模式,再写入数据。合并操作可能会破坏这个顺序,或者被硬件视为一个非法操作。同样,事务的地址、长度和大小也绝对不能改变。

4. 读数据不能预取

行为:当 CPU 读取状态寄存器时,系统只能读取该状态寄存器本身的值,而不能顺带读取相邻的、CPU 并未请求的寄存器。

举例:假设状态寄存器在 
0x1008
,而旁边有一个波特率寄存器在 
0x100C
。系统不能因为 CPU 读 
0x1008
 就一次性把 
0x1008
 和 
0x100C
 都读回来。因为对波特率寄存器的读取操作本身,在某些设计中可能就会产生不必要的副作用(尽管不常见),或者至少是浪费了总线带宽。

5. 写事务不能合并

行为:每个写操作都必须独立完成。

举例:如果 CPU 需要先后向两个不同的外设寄存器发送两个独立的命令,系统绝对不能将它们合并。每个命令都可能触发一个独立的硬件状态机,合并写入会使得硬件无法区分这两个命令,导致功能失效。

之所以设立如此严格的 
Device Non-bufferable
 类型,其核心原因是 保证与内存映射外设交互的绝对正确性

外设寄存器具有“副作用”

这是最根本的原因。普通的内存(如 RAM)访问的“作用”就是改变或读取存储单元的值。而外设寄存器则不同,一次写入可能意味着“启动一个引擎”、“清除一个标志”或“发送一个命令”;一次读取可能意味着“锁存当前输入”、“确认一个中断”或“读取一个瞬时状态”。

因为这些操作都有副作用,所以每一次访问都必须是精确的、原子的、不可分割的。任何优化(如合并、缓冲、预取)都可能改变这些副作用的次数和顺序,从而彻底改变硬件的运行状态。

保证操作的强顺序和可见性

在驱动程序中,操作外设的序列通常是严格设计好的。程序员需要确信,在收到一个写操作的响应后,前一个操作已经完成,后续操作可以安全进行。


Device Non-bufferable
 通过要求响应来自最终目的地,为软件提供了一个强大的同步点。这简化了软件编程模型,无需插入复杂的内存屏障指令就能保证操作的顺序。

读取必须反映实时状态

外设的状态(如“数据就绪”、“繁忙”、“错误”)是动态变化的。软件依赖读取这些状态来做决策。如果数据被缓存或预取,软件读到的就是一个“过时”的快照,将导致决策错误。

这些规则的诞生,直接源于计算机系统架构中 内存映射I/O 的引入及其带来的挑战。

内存映射I/O的普及

在早期计算机中,I/O 拥有独立的地址空间和专用的指令(如 x86 的 
IN
 和 
OUT
 指令)。这从硬件上就将内存访问和 I/O 访问分开了。

背景转变:现代处理器,特别是 RISC 架构(如 ARM),普遍采用内存映射I/O,即 CPU 使用普通的加载/存储指令来访问外设。这简化了 CPU 设计,但将区分内存和 I/O 的职责转移给了总线协议和内存系统

总线性能优化与硬件正确性的冲突

为了提升性能,计算机系统引入了缓存、写缓冲区、事务合并等高级特性。这些优化对于普通内存访问是巨大的福音。

出现的问题:当这些优化机制被不加区分地应用到外设访问上时,就会导致灾难性的后果。驱动工程师发现,他们的代码在理论上是正确的,但在实际的硬件上却无法工作,因为总线在“背后”重新排序和合并了他们的操作。

解决方案:总线协议必须提供一种机制,让软件能够显式地禁止所有这些优化。
Device Non-bufferable
 内存类型就是对这个问题的直接回答。它定义了一个“安全区”,在这个区域内的所有访问都按照程序员最直观的理解来执行,不做任何“聪明”的改动。

嵌入式与实时系统的需求

在嵌入式系统和实时系统中,硬件的时序行为必须是可预测的。一个写入操作需要多长时间能生效?一个状态读取是否能得到最新值?
Device Non-bufferable
 的严格规则为这些问题提供了确定的答案,使得设计满足实时性要求的系统成为可能。

note
Device Non-bufferable
 内存类型是 AXI 协议为协调 “高性能内存子系统” 和 “精确外设控制” 这对矛盾而设立的一道“防火墙”。它承认了外设世界的特殊性和脆弱性,并通过一系列禁止优化的规则,为软件与硬件的可靠交互提供了坚实的基础。可以说如果没有它,基于内存映射I/O的现代计算系统将无法稳定工作。

Device bufferable 内存类型的要求

Device Bufferable 类型同样用于访问内存映射外设,但它为写操作提供了一点灵活性,以换取性能提升,同时仍然保持对读操作事务原子性的严格要求。还是通过一个具体的场景来理解:

场景:一个 GPU 或 DMA 控制器需要向帧缓冲区 写入大量的像素数据。帧缓冲区是位于显示控制器内部的一块内存,用于存储屏幕上显示的图像。

现在逐条分析规则如何应用于此:

1. 写响应可以从一个中间点获得

行为:这是与 
Device Non-bufferable
 最核心的区别。当主设备(如GPU)写入数据时,一个中间的写缓冲区 可以立即返回一个“完成”响应,而不必等待数据真正到达最终的显示控制器。

举例:GPU 写入一个像素数据。系统中的一个互联结构或内存控制器内的写缓冲区接收了这个数据,并立刻告诉 GPU:“好了,写完了,你可以继续了”。GPU 因此被释放,可以立即开始处理下一个像素,而不需要等待相对较慢的显示控制器。

对比:如果是 
Device Non-bufferable
,GPU 必须等待显示控制器本身的响应,这会严重拖慢渲染速度。

2. 写事务必须及时在最终目的地变得可见

行为:这是一个重要的限制条件。虽然响应可以来自中间点,但数据不能无限期地停留在缓冲区里。它必须在一个“及时”的时间范围内被推送到最终目的地。

举例:对于帧缓冲区,这个“及时”通常意味着必须在下一帧屏幕刷新开始之前,将所有数据写入显存。如果缓冲区持有数据太久,屏幕就会显示上一帧的残缺图像或乱码。硬件设计必须保证缓冲区能在下一个垂直同步信号到来前被清空。

3. 读数据必须从最终目的地获得

行为:对于读操作
Device Bufferable
 和 
Device Non-bufferable
 完全没有区别。读取必须绕过所有缓冲区,直接访问外设。必须从写入的最新版本中获取;不得缓存数据以供以后读取

举例:如果 CPU 想要读取帧缓冲区的内容(例如用于截图),它必须直接从显示控制器的显存中读取,而不是从那个中间的写缓冲区读取。因为写缓冲区里的数据可能还不是最终图像的一部分。

4. & 5. & 6. 事务不可修改、不能预取、不能合并

行为:这些规则与 
Device Non-bufferable
 完全一致。

举例

不可修改:不能将多个像素写入合并成一个突发传输,因为显示控制器可能需要精确的每个像素的写入时序。

不能预取:读取显示控制器的配置寄存器时,不能顺带读取相邻的寄存器。

不能合并:对两个不同寄存器(如调色板索引和颜色值)的写入必须保持独立。

设立 
Device Bufferable
 这种“中间路线”类型,是为了在外设正确性系统性能之间取得一个平衡。

提升对大数据量外设的写入性能

这是最直接的原因。像帧缓冲区、高速网络接口的数据缓冲区这类外设,需要极高的写入带宽。如果每次写入都要等待最终目的地的响应,性能瓶颈将非常严重。允许缓冲使得主设备可以以接近其最高速度持续写入,从而极大提升了系统吞吐量。

在保证功能正确的前提下引入灵活性

它认识到,并非所有外设访问都像“启动命令”那样对延迟极度敏感。对于数据填充类的操作,只要数据最终能正确到达,稍微的延迟是可以接受的。

“及时”这个要求是一个实用性的妥协。它为硬件设计者提供了灵活性,同时又防止了数据被无限期延迟,确保了系统功能的最终正确性。

维持读操作和事务原子性的严格性

尽管放宽了写响应,但它坚守了所有其他设备内存的规则。这是因为:

读操作必须反映外设的实时状态,任何缓冲都会导致读到旧数据。

事务原子性(不可修改)是外设交互的基石,绝不能破坏。

这种分类的诞生,是系统架构师面对不同外设的带宽需求延迟敏感性所做的精细化权衡。

图形处理与高带宽外设的兴起

随着图形用户界面和多媒体应用的普及,帧缓冲区对总线带宽的要求呈指数级增长。

背景:传统的 
Device Non-bufferable
 模式成为了图形性能的瓶颈。硬件工程师需要一种方法来解决这个问题,但又不能破坏设备内存的基本语义。

解决方案
Device Bufferable
 应运而生。它专门为这类“高带宽、可容忍适度延迟”的数据传输场景而设计。这使得 GPU 和视频编解码器等组件能够高效工作。

复杂 SoC 互联中的延迟隐藏

在复杂的 SoC 中,数据从发起者到目的地可能需要经过多个时钟域和互联层次,路径很长。

背景:如果每个写操作都要穿越整个芯片并返回响应,延迟会非常高,严重制约系统性能。

解决方案:在互联结构中部署分布式的写缓冲区,并利用 
Device Bufferable
 属性,可以有效地隐藏这部分写入延迟。这成为了构建高性能 SoC 的标准实践。

对“及时性”的工程定义

“必须及时”这个要求是协议灵活性和工程智慧的体现。

背景:协议无法为一个所有外设定义统一的“及时”时间,因为不同的外设有不同的需求(帧缓冲区是16ms,音频缓冲区可能是1ms,网络包缓冲区可能是微秒级)。

解决方案:协议将“及时”的具体定义留作 IMPLEMENTATION DEFINED,由芯片设计者和外设供应商在数据手册中共同约定。这既保证了功能的正确性,又给予了硬件实现足够的自由度。

总结
Device Bufferable
 内存类型是 AXI 协议在认识到外设世界多样性的基础上,做出的一次精妙折衷。它在坚守设备内存正确性核心(读操作精确、事务原子性)的同时,为性能关键的写操作开了一扇窗。这种设计使得系统能够同时满足对精确控制有严苛要求的低速外设和对吞吐量有极高要求的高速外设,是复杂 SoC 设计能够成功的关键因素之一。

Normal Non-cacheable Non-bufferable 的内存类型

这种内存类型用于访问普通的、无副作用的存储器(如 RAM),但要求强一致性,同时允许系统进行某些性能优化。通过一个具体的场景来理解:

场景:一个多核处理器系统中的共享内存区域,用于核心间的通信。例如,一个核心作为生产者写入数据,另一个核心作为消费者读取数据。逐条分析规则如何应用于此:

1. 写响应必须来自最终目的地

行为:当生产者核心写入数据到共享内存时,它必须等待数据被真正写入主内存后,才能收到写完成响应。

举例:核心 A 向共享地址 
0x8000
 写入一个标志,表示“数据已准备好”。由于是 
Non-bufferable
,核心 A 会等待,直到这个写入操作穿透所有层级,到达最终的内存控制器并被确认。当核心 A 收到响应后,它可以确信数据已经对系统中所有其他主设备(如核心 B)可见。

关键:这提供了一个强大的同步点,确保了数据的强一致性。

2. 读数据必须从最终目的地获得

行为:当消费者核心从共享内存读取数据时,它读取的数据必须是来自主内存的最新版本。

举例:核心 B 从地址 
0x8000
 读取那个“数据已准备好”的标志。它会直接访问主内存,确保读到的是核心 A 刚刚写入的最新值,而不是任何一个核心的缓存中的可能过时的副本。

关键:这保证了消费者总能获得生产者发布的最新数据。

3. 事务是可修改的

行为:这是与 
Device
 内存类型的根本区别。系统可以为了效率而拆分合并这些事务。

举例

拆分:核心 A 发起一个长的读突发,但内存控制器处理能力有限,可以将其拆分成多个较短的突发。

合并:这是更重要的优化,见下一条。

4. 写事务可以合并

行为:系统可以将多个连续的、小的写操作合并成一个大的突发写入。

举例:核心 A 需要初始化一个数据结构,它连续执行了 4 次单次的 32 位写操作,地址分别是 
0x9000

0x9004

0x9008

0x900C

行为:系统中的一个互联或缓冲区可以探测到这些访问是连续的,于是将它们合并成一个单一的 4 拍突发写入,起始地址为 
0x9000

好处:总线效率大幅提升。原本需要 4 次独立的地址相位、数据相位和响应相位,现在只需要 1 次。这极大地减少了总线开销,提高了有效带宽。

这种内存类型的设计,是为了在强数据一致性适度的性能优化之间取得平衡。

保证多核环境下的强内存一致性

这是 
Non-bufferable
 和 
Non-cacheable
 的核心原因。通过强制读写都直达最终内存,它绕过了所有可能持有旧数据的缓存和缓冲区。这为多核间通信提供了最简单、最可靠的共享内存模型,无需复杂的缓存一致性协议来保证,从而简化了软件设计

在保证一致性的前提下提升性能

虽然它禁止了缓存和写缓冲区带来的异步性能优势,但它仍然通过 
Modifiable
 属性保留了事务级优化的可能性。

合并写入是其中最关键的性能优化。它解决了总线协议本身的开销问题,对于初始化大块内存或顺序写入数据流的场景,性能提升非常显著。

区分“数据”与“命令”

它与 
Device
 类型形成鲜明对比。
Device
 类型保护的是有副作用的“命令”访问,因此禁止所有修改。而 
Normal Non-cacheable Non-bufferable
 处理的是纯粹的“数据”,没有副作用,因此允许合并和拆分,只要最终结果一致即可。

这种内存类型的诞生,主要源于多核处理器异构计算系统对共享数据一致性的迫切需求。

多核处理器的早期挑战

在多核成为主流之初,硬件支持的全局缓存一致性协议(如 MESI)并非无处不在,或者在实现上代价高昂。

背景:软件工程师需要一种简单可靠的方式在核心间共享数据,而不必担心缓存一致性问题。

解决方案:将共享内存区域标记为 
Non-cacheable
 和 
Non-bufferable
。这确保了任何核心的写入都立即对全局可见,任何核心的读取都获得最新值。这是一种通过“牺牲性能(不缓存)来换取正确性和简化性”的经典权衡。

DMA 与 CPU 的协同工作

在 DMA 控制器与 CPU 共享数据的场景中,缓存一致性是一个大问题。

背景:如果 CPU 缓存了某块数据,然后 DMA 控制器直接向主内存写入新数据,CPU 将无法看到更新,因为它读的是自己的缓存副本。

解决方案:将 DMA 缓冲区设置为 
Normal Non-cacheable
。这样,CPU 对 DMA 缓冲区的任何访问都直接作用于主内存,从而与 DMA 控制器看到的数据视图保持一致。
Non-bufferable
 则进一步确保 CPU 的写入能立即被 DMA 控制器看到。

对总线效率的不懈追求

即使在这种强调一致性的模式下,系统设计者也不愿意完全放弃性能。

背景:如果完全禁止优化,连续的小型写入会成为严重的性能瓶颈。

解决方案:允许事务合并。这体现了 AXI 协议的设计智慧:即使在最严格的模式下,只要不违反该模式的核心语义(即强一致性),就应允许进行性能优化。事务合并不会改变最终写入内存的数据值和顺序,因此它与强一致性模型是兼容的。

总结
Normal Non-cacheable Non-bufferable
 内存类型是系统设计中的一个重要工具。它就像一把“安全锤”——当你需要绝对的数据一致性保证时(如多核同步、DMA 缓冲区),你会敲碎“性能”这层玻璃来使用它。它通过绕过缓存和写缓冲区来提供最强的一致性保证,同时又通过允许事务合并来尽可能地挽回一些性能损失。这种精细的控制能力,是构建复杂、可靠的多核系统的基石。

 Normal Non-cacheable Bufferable 内存类型。

这是一种在保证最终一致性的前提下,为实现高性能而设计的模式,其行为非常精妙。通过一个经典的 “生产者-消费者” 场景来理解所有这些规则。假设在一个多核系统中:

生产者:CPU Core A

消费者:CPU Core B

共享数据:一块位于主内存中的缓冲区,用于传递消息。这块内存被标记为 
Normal Non-cacheable Bufferable

现在,我们看 Core A(生产者)如何写入数据,以及 Core B(消费者)如何读取数据。

1. & 2. 写响应可从中间点获得,但必须及时到达最终目的地

生产者行为:Core A 向共享缓冲区写入消息。

系统中的一个写缓冲区 接收了这些数据,并立即返回写响应给 Core A。

Core A 收到响应后,就可以继续执行其他任务,感觉上消息已经“发送完毕”。

同时,写缓冲区在后台负责将这些数据最终写入主内存。规则要求这个过程必须是“及时”的。

关键:这提升了生产者的性能,但它引入了一个时间窗口:数据在写缓冲区中,尚未到达主内存。

3. 读数据可从最终目的地一个正在行进中的写事务获得

这是最核心、最精妙的规则。它定义了当消费者 Core B 读取时,系统如何解决数据一致性问题。

情况A:读最终目的地

如果 Core B 的读请求到达时,Core A 的数据已经被写缓冲区推入了主内存,那么 Core B 将从主内存读到最新数据。这是最简单的情况。

情况B:读“正在行进中的写事务”

场景:Core A 刚写完数据(已收到响应),但数据还在写缓冲区里“行进”。此时,Core B 发起了对同一地址的读请求。

行为:系统可以允许 Core B 的读请求直接从这个写缓冲区中获取数据,而不是去读主内存中可能存在的旧数据。

好处:性能极高!Core B 几乎瞬间就拿到了 Core A 刚刚生产的数据,无需等待数据慢速写入主内存。这实现了高效的数据共享。

4. 从写事务中读数据的附加规则

必须获取最近一次写入的版本:如果写缓冲区里有对同一地址的多次写入,Core B 必须读到最后一次写入的值。这保证了内存访问的顺序语义。

数据不得被缓存以服务后续读:从写缓冲区读出的数据是“一次性”的。它不能被存入一个缓存行,然后用于服务 Core B 后来的读请求。因为后续的读请求可能发生在数据已写入主内存后,必须从主内存读取以确保与其他潜在主设备的同步。这维护了 
Non-cacheable
 的根本属性。

5. & 6. 事务可修改A5.2.2、写事务可合并

行为:系统可以对这些访问进行所有优化。

举例:Core A 要写入消息头和数据,这可能是多个独立的写操作。系统可以将它们合并成一个突发传输,极大地提高了总线效率。

这种内存类型的设计,是为了实现一种 “弱一致性” 模型,在保证功能正确的前提下,最大化系统的并发性能和响应速度。

实现高性能的生产者-消费者模型

这是最主要的原因。通过允许读-写合并,它极大地减少了从生产到消费的延迟。生产者无需等待慢速内存,消费者也能立刻拿到新鲜数据。这对于流水线处理、消息传递等场景是巨大的性能提升。

在非缓存共享内存中平衡性能与一致性


Non-cacheable
 避免了复杂的缓存一致性问题,使得多主设备(CPU, DMA, GPU)共享内存变得简单。

但纯粹的 
Non-bufferable
 会迫使每次写入都等待内存,性能很差。


Bufferable
 解决了性能问题,但带来了数据“在途”的状态。

“从行进中写入读取”的规则,正是为了解决 
Bufferable
 引入的这个问题而设计的
。它确保了在弱一致性模型下,消费者总能见到它能见到的最新数据,实现了系统内的“因果一致性”。

提供灵活性而非强制性

规则中使用了“可以”而不是“必须”。这意味着硬件实现可以根据自己的架构选择是否实现这种读-写合并。一个简单的系统可能总是从最终目的地读,而一个高性能系统则会实现合并。这种灵活性使得协议能够适应不同性能级别的设计。

这种内存类型的诞生,源于对 高性能计算 和 多核编程模型 的深入研究,特别是对 内存一致性模型 的深刻理解。

对弱内存序模型的硬件支持

现代高性能处理器普遍采用弱内存序来挖掘并行性。这意味着事务的完成顺序可能与发出顺序不同。

背景:需要硬件机制来支持这种乱序,同时提供一个对程序员可见的、定义明确的一致性模型。

解决方案
Normal Non-cacheable Bufferable
 的行为正是弱内存序的一个典型体现。它明确规定了在数据到达全局可见的最终目的地之前,本地是可以看到它的。这要求软件工程师在需要强顺序的地方(如锁的释放)使用明确的内存屏障指令。

科学计算与高效数据共享的需求

在高性能计算中,核心间需要极低延迟地共享大量中间结果。如果每次共享都要等数据写入共享内存,延迟将无法忍受。

背景:系统需要一种比软件
memcpy
更高效的核心间通信机制。

解决方案:这种内存类型配合硬件实现的写缓冲和读-写合并,在硬件层面提供了类似“消息传递”的零拷贝机制,实现了极低延迟的数据共享。

复杂SoC中系统级缓冲区的出现

在复杂的SoC中,系统级缓存和写缓冲区变得普遍。这些组件位于核心私有的L1/L2缓存之外,主内存之内。

背景:必须有一套清晰的规则来定义这些系统级缓冲区如何与
Non-cacheable
的内存区域交互。

解决方案:AXI协议通过
Normal Non-cacheable Bufferable
类型精确地定义了这些规则:数据可以被缓冲,但不能被缓存;读取可以来自缓冲区,但不能为后续访问创建缓存副本。

总结
Normal Non-cacheable Bufferable
 是一种为高性能共享内存编程设计的专家级工具。它通过引入一个“在途”数据的状态,并允许聪明的读操作从这个状态中获取数据,成功地在一个简单的、无缓存的一致性模型上,实现了接近缓存系统的性能。它代表了协议设计者对现实世界性能需求的理解和妥协:在保证最终数据全局一致的前提下,最大限度地优化了最常见的数据共享路径(即一个生产者紧接着一个消费者)的性能。 使用这种模式需要对内存一致性有深入理解,但它也是构建低延迟多核系统的关键之一。

Write-Through No-Allocate 

这一内存类型,是一种在缓存策略上非常精细和微妙的模式。通过一个具体的场景来理解这种内存类型的所有行为。假设一个多核处理器正在处理一个流式数据,例如网络数据包或视频解码的中间数据流。

场景:CPU Core A 正在处理一个网络数据包,它需要将处理后的数据写入内存,以便网络控制器DMA能够读取并发送。这个数据块很大,且每个数据包通常只处理一次。

现在,我们逐条分析规则:

1. 写响应可以从一个中间点获得

行为:当 Core A 写入数据时,一个中间级(如 L2 缓存或系统级缓存)可以接收数据并立即返回“完成”响应,而不必等待数据被写入最终的主内存。

举例:Core A 写入一个处理后的数据段。L2 缓存接收了这个数据,并立刻响应 Core A。Core A 可以继续处理下一个数据段,吞吐量很高。

2. 写事务必须及时在最终目的地变得可见(但无确认机制)

行为:这是一个关键限制。虽然响应可以提前,但 L2 缓存必须在“及时”的时间内将数据写透到主内存。

举例:网络控制器 DMA 需要读取这个数据包并发送出去。L2 缓存必须保证在 DMA 控制器发起读取之前,数据已经被更新到主内存中。

关键矛盾:协议明确指出 “没有机制来确定何时在最终目的地可见” 。这意味着软件(驱动程序)无法确切知道数据何时对 DMA 控制器完全可见。它必须依赖其他同步机制(如内存屏障或轮询状态位)来确保安全性。

3. 读数据可以从一个缓存的副本中获得

行为:如果数据还在缓存中,后续的读操作(来自 Core A 自己或其他核心)可以直接从缓存中获取,速度极快。

举例:在 Core A 写完数据后,Core B 需要读取它来进行校验。如果数据还在 L2 缓存中,Core B 可以直接从 L2 读取,无需访问慢速的主内存。

4. & 5. & 6. 事务可修改、可预取、可合并

行为:系统可以对这类内存访问进行所有常见的性能优化。

举例:Core A 的多个小写入可以被合并成突发传输;CPU 可以预取数据到缓存以减少等待时间。

7. 读写事务都需要进行缓存查找

行为:这是 
Write-Through
 模式的本质。缓存必须参与每一次访问,以维护数据一致性。

举例:当 Core A 写入时,缓存必须查找该地址是否已被缓存。如果是,则更新缓存行并同时写入主内存。当 Core B 读取时,缓存也必须查找,如果存在有效副本就提供它。

8. No-Allocate 属性是一个分配提示

行为:这是这种内存类型的精髓。它建议缓存,在发生缓存未命中时,不要为数据分配新的缓存行。但这不是强制禁止的。

举例:Core A 写入一个全新的、不在任何缓存中的数据。

缓存的行为(遵循建议):缓存接收数据,将其直接写入主内存,但在 L1 或 L2 缓存中为这个数据分配一个缓存行。

好处:宝贵的缓存空间没有被这个“一次性”的流数据所占用。缓存行留给了更可能被重复访问的代码和数据结构。

缓存的行为(不遵循建议):如果系统判断这个访问模式可能有局部性,它仍然可以选择分配一个缓存行。

设立这种复杂模式的核心原因是为了在 性能一致性 和 缓存效率 之间取得一个非常精细的平衡。

为流数据提供高性能与缓存保护

这是 
No-Allocate
 提示存在的首要原因。对于没有时间局部性的大型数据流,如果盲目缓存,会迅速污染缓存,将更有价值的数据挤出去,导致性能急剧下降(缓存颠簸)。
No-Allocate
 提示允许系统在享受缓存带来的读写加速(如果数据碰巧在缓存里)的同时,避免在缓存未命中时污染缓存。

保证数据一致性,简化多核编程


Write-Through
 策略确保了任何写入都会立即更新到主内存。这使其他主设备(如另一个CPU核心、DMA、GPU)总能从主内存看到最新数据,简化了它们之间的数据共享和一致性管理。这是一种比 
Write-Back
 更简单、更“笨”但更安全的一致性模型。

平衡写入延迟和一致性

允许写响应来自中间点,提升了写入方的体验(低延迟)。强制写入最终目的地,保障了读取方的体验(数据强一致性)。这是一种经典的权衡。

这种内存类型的诞生,直接源于对大数据流处理多核共享工作负载的深入理解。

网络和多媒体处理的驱动

网络数据包、视频帧、音频样本等数据通常体积庞大,且每个数据单元通常只被访问一次(或次数很少)。

背景:传统的缓存算法基于“局部性原理”,但这类数据破坏了时间局部性。系统需要一种机制来告诉硬件:“这个数据流不值得长期缓存”。

解决方案
No-Allocate
 提示应运而生。它让软件能够向硬件传达其对数据访问模式的认知,从而实现更智能的缓存管理。

异构系统与I/O一致性

在包含DMA控制器、GPU等I/O协处理器的系统中,保证I/O设备能看到CPU写入的最新数据是一个经典难题。

背景:使用 
Write-Back
 缓存时,数据可能长期停留在CPU缓存中,I/O设备读到的将是主内存中的旧数据,导致错误。

解决方案:将需要与I/O设备共享的内存区域设置为 
Write-Through
,可以强制CPU写入直达主内存,从而保证I/O设备总能获取正确数据。同时,加上 
No-Allocate
 提示,可以避免这些不常被CPU访问的共享缓冲区占用宝贵的缓存空间。

对“及时性”的硬件信任

“没有机制来确定…” 这一条规则,深刻地反映了协议设计者对硬件实现的信任和妥协。

背景:如果要求软件能精确知道每次写入何时到达主内存,就需要极其复杂的硬件状态报告机制,这会带来巨大的性能和面积开销。

解决方案:协议将“及时”的定义交给实现,并相信硬件设计者会做出合理的选择(例如,在几打时钟周期内完成)。软件则通过更高层次的同步原语(如门铃中断)来管理数据依赖,而不是纠结于单次写入的精确时刻。

总结
Write-Through No-Allocate
 是一种为特定工作负载量身定制的专家级工具。它非常适合处理那些需要被多个代理共享对缓存不友好的数据。它通过写透模式保证了数据的强一致性,又通过“不分配”的建议保护了稀缺的缓存资源,同时允许各种事务优化以维持较高的总线效率。使用这种模式需要对数据访问模式有深刻的理解,但它也是构建高性能、高效率通信和数据处理系统的关键之一。

Write-Through Read-Allocate 

这种内存类型。它是在 Write-Through 基础上对缓存分配策略的进一步优化。Write-Through Read-Allocate 在基础行为上与 Write-Through No-Allocate 完全一致,但在缓存分配建议上更加精细。它专门针对读多写少的工作负载进行了优化。

让我们通过一个具体的场景来理解:

场景:一个程序代码段或者一个频繁被查询的配置数据表。这些数据被多个CPU核心频繁读取,但很少被修改。

现在,我们看这种内存类型如何工作:

基础行为(与 Write-Through No-Allocate 相同)

写响应可从中间点获得:写入时,缓存可以立即响应,数据随后透写到主内存。

读数据可从缓存副本获得:如果数据在缓存中,读取速度极快。

事务可修改、可合并:系统可以进行各种性能优化。

核心差异:分配建议

对读事务的建议:推荐分配

行为:当发生读未命中时(即CPU要读的数据不在缓存中),缓存强烈建议为此数据分配一个新的缓存行。

举例:CPU Core A 第一次执行某个函数,需要从内存读取指令。由于是 
Read-Allocate
,系统会在L1/L2缓存中为这段代码分配缓存行。当Core A 再次执行这个函数,或者 Core B 也要执行它时,指令已经在缓存中了,可以实现极速访问。

效果:这极大地提升了频繁读取数据的性能,符合程序的局部性原理。

对写事务的建议:不推荐分配

行为:当发生写未命中时(即CPU要写入的数据不在缓存中),缓存不建议为此分配新的缓存行。

举例:程序员偶尔需要更新一下那个配置表中的一个值。当CPU执行这个写操作时,如果配置表不在缓存中,系统会选择将其整个缓存行加载到缓存,而是直接将这个新的写入数据透写到主内存。

效果:避免了为了一个偶尔的、孤立的写操作,而将一大块可能不再被读取的数据塞进宝贵的缓存空间,从而防止了缓存污染

这种精细分类的核心原因是根据数据的访问模式来最优化地使用稀缺的缓存资源

为读密集型工作负载提供最佳性能

对于代码、常量数据、查找表等,读取频率远远高于写入频率。
Read-Allocate
 确保这些数据一旦被访问,就会留在缓存中,为后续的大量读取请求提供高速服务。这是对缓存空间最高效的利用。

避免由零星写入造成的缓存污染

如果一个数据块主要是只读的,那么对其偶尔的写入操作就不值得为它分配一个长期的缓存席位。
Write-No-Allocate
 的建议确保了这些“过客”数据不会把更重要的常驻数据从缓存中挤出去。

保持 Write-Through 的一致性优势

它继承了 Write-Through 的所有优点:写操作立即对系统中所有其他主设备(通过主内存)可见,简化了多核环境下的数据一致性管理。

这种内存类型的诞生,源于编译器、操作系统开发者和硬件设计师对程序行为模式的长期观察和总结。

对程序行为“局部性原理”的深度利用

计算机科学的一个基石是“局部性原理”,包括时间局部性(最近访问的数据很可能再次被访问)和空间局部性(访问一个数据时,其相邻的数据也很可能被访问)。

背景:代码和只读数据是展现极强时间局部性的典型代表。一个函数被调用一次后,很可能在短时间内被再次调用。

解决方案
Read-Allocate
 就是硬件对时间局部性的直接响应。它假设:“既然你读了一次,你很可能再读一次,那我就把你留下来。”

操作系统与编译器的协作需求

操作系统负责管理内存属性,而编译器了解数据的访问特性(例如,通过 
const
 关键字可以知道某些数据是只读的)。

背景:需要一种机制,将软件层面的知识传递给硬件内存系统。

解决方案:操作系统可以将程序的代码段和只读数据段的内存属性设置为 
Write-Through Read-Allocate
。这样,硬件就能以最优的方式处理这些数据,无需复杂的预测算法。

嵌入式系统中对性能/功耗的精细控制

在嵌入式领域,缓存的大小和功耗都受到严格限制。每一份缓存资源都必须用在刀刃上。

背景:需要一种比通用缓存策略更高效的、针对特定数据模式的缓存管理方法。

解决方案
Write-Through Read-Allocate
 这样的细分类型使得嵌入式系统开发者能够为每一块内存区域“量体裁衣”,从而实现极致的性能优化和功耗控制。例如,将中断向量表设置为这种模式,可以保证中断响应总是很快,同时又不会因为偶尔更新一个向量项而破坏缓存内容。

总结
Write-Through Read-Allocate
 不是一种通用的内存类型,而是一种为特定访问模式(读多写少)设计的专家级优化工具。它体现了计算机架构中一个深刻的思想:通过将软件的知识(数据的预期使用方式)编码到硬件属性中,来指导硬件进行更智能的决策。 当你能准确识别出系统中的代码段、常量配置表等读多写少的数据时,使用这种内存类型可以带来显著的内存子系统性能提升。

Write-Through Write-Allocate 

这种内存类型。它同样基于 Write-Through 模式,但在缓存分配策略上采取了与 Read-Allocate 几乎相反的优化方向。Write-Through Write-Allocate 专门为写多读少的工作负载进行了优化。让我们通过一个具体的场景来理解:

场景:一个视频帧缓冲区动态生成的中间数据缓冲区。CPU 或 GPU 需要连续地向其中写入大量数据(如渲染帧),但很少会回头读取这些数据。

现在,我们看这种内存类型如何工作:

基础行为(与 Write-Through No-Allocate 相同)

写响应可从中间点获得:写入时,缓存可以立即响应,数据随后透写到主内存。

读数据可从缓存副本获得:如果数据碰巧在缓存中,读取可以很快。

事务可修改、可合并:系统可以进行各种性能优化。

核心差异:分配建议

对写事务的建议:推荐分配

行为:当发生写未命中时(即CPU要写入的数据不在缓存中),缓存强烈建议为此数据分配一个新的缓存行。

举例:CPU 正在渲染一帧图像,需要向帧缓冲区的一个新区域写入像素。由于是 
Write-Allocate
,系统会在缓存中为这个区域分配缓存行。当 CPU 继续写入相邻像素时,这些写入会命中刚刚分配的缓存行,速度极快。

效果:这利用了程序的空间局部性。对一个位置的写入,很可能紧接着对相邻位置的写入。通过分配缓存行,将多次独立的写入转换为高效的缓存命中写入,大大提升了写入性能。

对读事务的建议:不推荐分配

行为:当发生读未命中时(即CPU要读取的数据不在缓存中),缓存不建议为此分配新的缓存行。

举例:偶尔需要检查一下帧缓冲区的某个特定值(如一个状态标志)。如果这个数据不在缓存中,系统会选择直接从主内存读取,而将其缓存起来。

效果:避免了为了偶尔的、孤立的读取操作,而将一大块不会被重复读取的数据塞进宝贵的缓存空间,从而防止了缓存污染

这种精细分类的核心原因是识别并优化一种特定的数据访问模式:顺序写入或填充缓冲区

为写密集型工作负载提供最佳性能

对于帧缓冲区、网络数据包缓冲区、计算中间结果等场景,主要操作是连续写入。
Write-Allocate
 确保了这些顺序写入能够充分利用缓存,将多次总线事务合并,减少总线开销,实现最高的写入吞吐量。

避免由零星读取造成的缓存污染

如果一个数据块主要是只写的,那么对其偶尔的读取操作就不值得为它分配一个长期的缓存席位。
Read-No-Allocate
 的建议确保了这些“过客”数据不会把更重要的常驻数据从缓存中挤出去。

保持 Write-Through 的一致性优势

它继承了 Write-Through 的所有优点:写操作对系统中所有其他主设备(通过主内存)立即可见,这对于需要与DMA控制器、显示控制器等外设共享数据的场景至关重要。

这种内存类型的诞生,源于对图形处理网络通信流式计算等特定工作负载的深入分析和优化需求。

图形渲染与帧缓冲区管理的需求

在图形学中,渲染一帧图像本质上是一个巨大的顺序写入操作。CPU或GPU需要填充一个二维像素阵列。

背景:如果没有 
Write-Allocate
,每个像素的写入都可能是一次缓存未命中,直接写入主内存,性能极差。

解决方案:将帧缓冲区标记为 
Write-Through Write-Allocate
。这样,当渲染开始触及一个新的缓存行时,该行被分配进缓存,后续对该行内像素的写入都变成高速的缓存命中写入,最后整个缓存行被一次性透写回主内存。这实现了近乎最优的写入性能。

网络协议栈与数据包处理

网络接口驱动程序需要组装外发的数据包,这同样是一个顺序写入缓冲区的过程。

背景:数据包一旦发送,就很少再被读取。缓存这些数据是浪费的。

解决方案:使用 
Write-Through Write-Allocate
 来优化数据包的组装过程(写入性能高),同时通过 
Read-No-Allocate
 避免在偶尔检查数据包头部时污染缓存。

科学计算与流式工作负载

在许多科学计算和数据处理任务中,程序会顺序填充一个大型数组或矩阵,然后将其交给下一个处理阶段(如DMA传输或另一个计算单元),之后便不再访问。

背景:需要一种模式来优化“生产”阶段的速度,同时不影响系统中其他并发任务的缓存效率。

解决方案
Write-Through Write-Allocate
 完美契合这种“一写不读”或“一写少读”的访问模式。

总结
Write-Through Write-Allocate
 是一种为写密集型、顺序访问模式量身定制的专家级工具。它体现了计算机架构中“为常见情况优化”的核心原则。通过建议缓存为写入分配资源,而为读取保守行事,它在不牺牲 Write-Through 一致性模型的前提下,为帧缓冲区填充、数据包组装等关键操作提供了最大的性能提升。当你能准确识别出系统中的“生产者”缓冲区(主要被写入,很少被读取)时,使用这种内存类型可以带来显著的性能收益。

Write-Through Read and Write-Allocate 

这种内存类型。这是 Write-Through 模式下最“积极”的一种缓存策略。Write-Through Read and Write-Allocate 在基础行为上与其它 Write-Through 类型一致,但它采取了最激进的缓存分配建议:无论是读还是写,只要发生缓存未命中,都建议分配缓存行。 这是为读写混合频繁的工作负载设计的。

让我们通过一个具体的场景来理解:

场景:一个高度优化的共享工作队列或一个频繁更新的热点数据结构。在一个多核系统中,多个CPU核心需要频繁地从这个队列中获取任务(读),并向其中放入新任务或更新任务状态(写)。

现在,我们看这种内存类型如何工作:

基础行为(与 Write-Through No-Allocate 相同)

写响应可从中间点获得:写入时,缓存可以立即响应,数据随后透写到主内存。

读数据可从缓存副本获得:如果数据在缓存中,读取速度极快。

事务可修改、可合并:系统可以进行各种性能优化。

核心差异:积极的分配建议

对读事务的建议:推荐分配

行为:当发生读未命中时,缓存建议分配新行。

举例:CPU Core B 第一次从工作队列中获取任务。由于是 
Read-Allocate
,系统会在 Core B 的缓存中为这个任务数据分配缓存行。当 Core B 需要再次检查这个任务的状态时,就可以直接从自己的缓存中读取,速度极快。

对写事务的建议:推荐分配

行为:当发生写未命中时,缓存同样建议分配新行。

举例:CPU Core A 要向工作队列添加一个新任务。如果队列的该部分不在缓存中,系统会分配一个缓存行,然后将新任务写入这个缓存行。如果 Core A 紧接着要更新这个任务的另一个字段,这次写入就会命中缓存,非常高效。

综合效果:这种“双分配”策略确保了这块内存区域一旦被访问(无论是读还是写),就会在缓存中建立起一个副本,为后续的所有访问(无论是读还是写)提供加速。这最大限度地利用了缓存来减少访问延迟。

这种策略的核心目标是针对访问频率极高共享数据,提供极致的低延迟访问体验,同时维持一致性的安全边界。

为读写混合型热点数据提供最大性能

这是最主要的原因。对于系统中最关键、最常被访问的那些“热点”数据(如操作系统调度器使用的运行队列、数据库的索引节点),其访问模式是读和写交织在一起的。积极的分配策略确保了无论访问方向如何,都能享受到缓存带来的速度提升。

最大化缓存的投资回报率

缓存是稀缺资源。这种策略的理念是:对于那些已经被识别出的、极其重要的数据,就应该毫无保留地使用缓存。既然这块内存区域的访问频率如此之高,那么为其分配缓存行就是最值得的投资,能够换来最大的整体性能收益。

在简化的一致性模型中追求性能

它依然建立在 
Write-Through
 的简单一致性模型之上。虽然 
Write-Back
 模式的写入性能可能更高,但其一致性协议也更复杂。
Write-Through Read and Write-Allocate
 提供了一个折中方案:在保持写入全局可见性(简化了多核编程)的同时,通过积极的缓存分配来尽可能地弥补写入性能的损失。

这种内存类型的诞生,源于对操作系统内核高性能服务器软件工作负载的深刻洞察。

操作系统内核数据结构的优化需求

操作系统内核中充满了被频繁读写的共享数据结构,例如:

进程描述符:经常被读取以获取状态,也经常被写入以更新状态。

虚拟内存管理区的数据结构:在页面分配和释放时被频繁更新和查询。

文件系统缓存元数据:如inode,被反复读取和修改。

背景:这些数据结构的性能直接决定了整个系统的响应速度。需要一种能同时优化读写操作的内存属性。

解决方案
Write-Through Read and Write-Allocate
 成为了这些关键数据结构的理想选择,因为它能确保它们大部分时间都驻留在高速缓存中。

多核服务器工作负载的驱动

在Web服务器、数据库等应用场景中,存在大量的共享计数器、锁结构和会话状态。这些数据被所有工作线程频繁地竞争访问(读-改-写)。

背景:减少对这些共享数据的访问延迟,意味着更高的请求处理吞吐量和更低的延迟。

解决方案:使用这种积极缓存策略,可以确保即使在有写入竞争的情况下,每个核心也能在本地缓存中完成一部分操作,从而减轻总线压力,提升整体并发能力。

在安全与性能间的权衡


Write-Through
 模式因其数据的“立即可见性”,在需要与I/O设备共享数据或在对系统一致性要求极高的安全关键型系统中,比 
Write-Back
 更受青睐。

背景:在这些不能使用 
Write-Back
 的场景中,如何尽可能地提升性能?

解决方案
Write-Through Read and Write-Allocate
 提供了答案。它是在 
Write-Through
 约束下所能采取的最极致的性能优化策略。

总结
Write-Through Read and Write-Allocate
 是一种“全力以赴”的缓存策略。它不像其他类型那样小心翼翼地区分读写模式来防止缓存污染,而是假设它所应用的内存区域是系统中当之无愧的“VIP”,值得占用宝贵的缓存空间。当你能明确识别出系统中的性能关键路径上的读写混合热点数据时,使用这种内存类型可以带来显著的性能提升,是优化系统核心瓶颈的强大工具。

 Write-Back No-Allocate 

这种内存类型。这是 AXI 协议中性能潜力最高但也最复杂的内存类型之一。Write-Back No-Allocate 结合了回写缓存的高性能特性和针对特定数据模式的优化建议。它非常适合处理大型的、流式的、一次性的数据。

让我们通过一个具体的场景来理解:

场景:一个视频解码器正在解码一帧高清视频。解码过程中会产生大量的中间像素数据,这些数据会被后续的处理阶段使用,但每个中间数据块通常只被访问有限次数,然后就不再需要了。

现在,我们逐条分析规则如何应用于此:

1. 写响应可以从一个中间点获得

行为:当视频解码器写入一个中间像素块时,缓存可以立即接收数据并返回“完成”响应。

举例:解码器写入一个 8×8 的像素块。L2 缓存接收了这些数据,并立刻响应解码器。解码器可以继续处理下一个块,吞吐量很高。

2. 写事务不需要在最终目的地变得可见

行为:这是 
Write-Back
 与 
Write-Through
 的根本区别。数据可以停留在缓存中,不需要立即写入主内存。

举例:解码器写入的中间像素数据被保留在 L2 缓存中。只要后续的处理阶段(如运动补偿)也从同一个缓存中读取这些数据,系统就完全不需要访问慢速的 DDR 内存。

好处极低的写入延迟极高的带宽,同时大大减少了内存访问次数和功耗。

3. 读数据可以从一个缓存的副本中获得

行为:如果请求的数据恰好在缓存中,读取操作会非常快。

举例:运动补偿单元需要读取之前解码器产生的中间像素块。如果这些数据还在 L2 缓存中,读取几乎是瞬间完成的。

4. & 5. & 6. 事务可修改5.2.2、可预取、可合并

行为:系统可以进行所有常见的性能优化。

举例:解码器的多个小写入可以被合并;系统可以智能地预取数据到缓存。

7. 读写事务都需要进行缓存查找

行为:缓存必须参与每次访问,以维护其内部的一致性。

举例:当解码器写入时,缓存会检查该地址是否已存在。如果存在且是“脏”的,可能需要先写回内存,然后再处理新写入。

8. No-Allocate 属性是一个分配提示

行为:这是这种内存类型的精髓。它强烈建议缓存,在发生缓存未命中时,不要为数据分配新的缓存行。

举例:视频解码器开始处理一个全新的宏块,其数据不在任何缓存中。

缓存的行为(遵循建议):缓存接收解码器写入的数据,但为其在 L1/L2 缓存中分配一个长期的缓存行。它可能使用一个特殊的、短期的缓冲区来暂存这些数据,或者直接将其写入主内存(尽管不是必须的)。

好处:宝贵的缓存空间没有被这些“一次性”的流数据所占用。缓存行留给了更可能被重复访问的数据,如解码器的查找表或常用指令。

为什么不是强制性的? 硬件可能探测到强烈的空间局部性(比如连续写入),并认为分配一行缓存来服务后续的写入是值得的。这个“提示”给了硬件灵活性。

这种内存类型的设计,是为了在追求极致性能的同时,智能地管理最稀缺的缓存资源。

为流数据处理提供高性能同时保护缓存

这是 
No-Allocate
 提示存在的核心原因。对于视频帧、网络数据包、科学计算中间结果等大型数据流,如果盲目缓存,会迅速污染缓存,导致系统性能反而下降。
Write-Back No-Allocate
 允许数据享受回写缓存的高效路径,同时又避免长期占用缓存空间。

实现极致的写入性能


Write-Back
 模式本身就提供了最好的写入性能,因为写入只到缓存为止。结合事务合并等优化,它可以实现接近理论极限的写入带宽。

平衡灵活性与效率

将 
No-Allocate
 定义为“提示”而非“命令”,是协议实用主义的体现。它允许简单的硬件实现选择忽略此提示,同时也为复杂的硬件提供了根据实际访问模式动态调整的可能性,从而实现最佳的整体效率。

这种内存类型的诞生,是计算机架构应对大数据流处理异构计算挑战的直接结果。

多媒体和图形处理的爆炸式增长

随着高清视频、3D图形渲染的普及,系统需要处理的数据量急剧增加。这些数据有很强的空间局部性(连续访问),但很弱的时间局部性(很少重复访问)。

背景:传统的缓存算法会将这些数据全部缓存,导致核心算法和数据(如命令队列、几何数据)被挤出缓存,反而降低了整体性能。

解决方案
Write-Back No-Allocate
 提供了完美的解决方案。它让 GPU 或视频编解码器能够高速处理流数据,而不会破坏 CPU 的缓存环境。

多级缓存层次结构的复杂性

现代处理器拥有复杂的多级缓存(L1, L2, L3, 系统级缓存)。

背景:需要一种机制来指导数据应该在缓存的哪一级停留,或者是否应该进入缓存。

解决方案
No-Allocate
 提示可以作用在不同的缓存层级。例如,一个设计可能选择在 L1 缓存中遵循不分配的建议,但在共享的 L2 缓存中允许分配,从而在核心私有缓存和共享缓存之间实现智能的数据放置。

能效要求的驱动

在移动设备和数据中心,能效至关重要。访问主内存(尤其是 DDR)的功耗远高于访问缓存。

背景:需要在减少内存访问和减少缓存动态功耗之间取得平衡。

解决方案
Write-Back No-Allocate
 模式通过减少不必要的缓存分配,降低了缓存的动态功耗(更少的缓存行被激活)。同时,通过 
Write-Back
 策略本身减少了访问主内存的次数。这种组合实现了性能与功耗的最佳平衡。

总结
Write-Back No-Allocate
 是一种为专家级性能调优设计的精密工具。它最适合处理那些容量巨大、访问模式具有顺序性、且重用价值低的数据流。当你能准确识别出工作负载中的这类数据时,使用这种内存类型可以带来显著的性能提升和能效优化。它代表了缓存管理艺术的最高水平:不仅在数据命中时提供高速访问,更重要的是在数据未命中时做出智能的决策,以保护整个内存子系统的长期健康和高性能。

 Write-Back Read-Allocate 

这种内存类型。这是在 Write-Back 高性能基础上,专门为读多写少工作负载优化的缓存策略.Write-Back Read-Allocate 在基础行为上与 Write-Back No-Allocate 完全一致,但在缓存分配建议上更加精细。它专门针对那些主要被读取,但偶尔需要更新的数据进行优化。

让我们通过一个具体的场景来理解:

场景:一个游戏引擎中的材质资源库或一个数据库的索引结构。这些数据被 GPU 或 CPU 核心频繁读取以进行渲染或查询,但只有在资源更新或索引重建时才会被写入。

现在,我们看这种内存类型如何工作:

基础行为(与 Write-Back No-Allocate 相同)

写响应可从中间点获得:写入时,缓存可以立即响应,数据可以暂留在缓存中。

写事务不需要在最终目的地可见:这是 Write-Back 的核心优势,数据可以长期停留在缓存中。

读数据可从缓存副本获得:如果数据在缓存中,读取速度极快。

事务可修改、可预取、可合并:系统可以进行各种性能优化。

核心差异:分配建议

对读事务的建议:推荐分配

行为:当发生读未命中时(即请求的数据不在缓存中),缓存强烈建议为此数据分配一个新的缓存行。

举例:GPU 第一次需要渲染一个石头材质。由于是 
Read-Allocate
,系统会在 GPU 缓存中为这个材质数据分配缓存行。当后续帧需要再次渲染这个石头时,材质数据已经在缓存中了,可以实现极速访问。

效果:这极大地提升了频繁读取数据的性能,确保了热点资源常驻缓存。

对写事务的建议:不推荐分配

行为:当发生写未命中时(即要写入的数据不在缓存中),缓存不建议为此分配新的缓存行。

举例:游戏开发者更新了石头材质的颜色。当 GPU 执行这个更新时,如果材质数据不在缓存中,系统会选择将其整个缓存行加载到缓存,而是可能通过一个特殊的写合并缓冲区来处理这个孤立的写入操作。

效果:避免了为了一个偶尔的、孤立的写操作,而将一大块可能不再被读取的数据塞进宝贵的缓存空间,从而防止了缓存污染

这种精细分类的核心原因是根据数据的实际使用模式来最优化地使用缓存资源,同时享受 Write-Back 模式的高性能优势。

为读密集型工作负载提供极致性能

对于材质库、代码段、数据库索引等,读取频率远远高于写入频率。
Read-Allocate
 确保这些数据一旦被访问,就会留在缓存中,为后续的大量读取请求提供高速服务。

保护缓存免受零星写入的污染

如果一个数据块主要是只读的,那么对其偶尔的写入操作就不值得为它分配一个长期的缓存席位。
Write-No-Allocate
 的建议确保了这些”过客”数据不会把更重要的常驻数据从缓存中挤出去。

享受 Write-Back 的性能优势

与 Write-Through 不同,Write-Back 允许数据长期停留在缓存中,只有在必要时才写回主内存。这进一步减少了内存带宽的消耗和访问延迟,为读操作提供了比 Write-Through 更好的性能。

这种内存类型的诞生,源于对内容创作应用数据密集型服务工作负载的深入分析。

游戏和图形应用的资源管理

在现代游戏中,材质、网格、着色器等资源数量巨大,但只有一部分在特定场景中是活跃的。

背景:需要一种策略,确保活跃资源保留在缓存中,同时避免不活跃资源的更新操作污染缓存。

解决方案
Write-Back Read-Allocate
 完美契合这种需求。当资源被使用时,它们被缓存以加速读取;当资源被修改时(如美术师更新纹理),这些更新不会不必要地占用缓存空间。

数据库管理系统的优化

数据库的 B+树索引等结构在正常操作中被频繁遍历(读取),但只有在数据插入、删除或更新时才需要修改。

背景:需要最大化查询性能(读取),同时最小化维护操作(写入)对缓存的影响。

解决方案:使用 
Write-Back Read-Allocate
 可以确保索引的热点部分常驻缓存,加速查询;而索引的维护操作则不会不必要地占用缓存行。

操作系统的虚拟内存管理

操作系统的页表通常被频繁读取(地址转换),但只有在进程创建、销毁或内存映射变化时才被写入。

背景:需要确保地址转换的速度,同时避免页表更新操作影响系统性能。

解决方案:将页表内存标记为 
Write-Back Read-Allocate
,可以在保持高性能地址转换的同时,最小化页表更新对缓存的影响。

大规模Web服务的工作负载特征

Web服务中的用户配置、商品信息等数据通常被大量读取,但相对较少更新。

背景:需要优化缓存使用以服务大量读取请求。

解决方案
Write-Back Read-Allocate
 允许这些数据长期保留在缓存中,提供快速的读取访问,同时确保更新操作不会不必要地影响缓存效率。

总结
Write-Back Read-Allocate
 是一种为读密集型、偶尔更新的工作负载设计的精密优化工具。它将 Write-Back 模式的高性能潜力与智能的缓存分配建议相结合,在确保高频读取数据享受最佳性能的同时,保护缓存免受低频写入操作的污染。当你能准确识别出系统中的热点只读数据(如资源文件、代码库、配置数据)时,使用这种内存类型可以带来显著的系统性能提升,是构建高性能、高响应性系统的关键优化手段之一。

Write-Back Write-Allocate 

这种内存类型。这是在 Write-Back 高性能基础上,专门为写多读少工作负载优化的缓存策略。Write-Back Write-Allocate 在基础行为上与 Write-Back No-Allocate 完全一致,但在缓存分配建议上采取了专门针对写入操作优化的策略。它非常适合顺序写入或填充缓冲区的场景。

让我们通过一个具体的场景来理解:

场景:一个视频编码器正在将处理后的视频数据写入输出缓冲区,或者一个数据采集系统正在将传感器读数写入数据记录缓冲区。

现在,我们看这种内存类型如何工作:

基础行为(与 Write-Back No-Allocate 相同)

写响应可从中间点获得:编码器写入数据时,缓存可以立即响应。

写事务不需要在最终目的地可见:数据可以长期停留在缓存中。

读数据可从缓存副本获得:如果数据在缓存中,读取速度极快。

事务可修改、可预取、可合并:系统可以进行各种性能优化。

核心差异:分配建议

对写事务的建议:推荐分配

行为:当发生写未命中时(即要写入的数据不在缓存中),缓存强烈建议为此数据分配一个新的缓存行。

举例:视频编码器开始向一个新的内存区域写入编码后的数据块。由于是 
Write-Allocate
,系统会在缓存中为这个数据块分配缓存行。当编码器继续写入相邻数据时,这些写入会命中刚刚分配的缓存行,速度极快。

效果:这利用了程序的空间局部性。对一个位置的写入,很可能紧接着对相邻位置的写入。通过分配缓存行,将多次独立的写入转换为高效的缓存命中写入,大大提升了写入性能。

对读事务的建议:不推荐分配

行为:当发生读未命中时(即请求的数据不在缓存中),缓存不建议为此分配新的缓存行。

举例:偶尔需要验证编码数据的正确性,读取缓冲区中的某个特定值。如果这个数据不在缓存中,系统会选择直接从主内存读取,而将其缓存起来。

效果:避免了为了偶尔的、孤立的读取操作,而将一大块不会被重复读取的数据塞进宝贵的缓存空间,从而防止了缓存污染

这种精细分类的核心原因是识别并优化一种特定的数据访问模式:顺序写入或填充缓冲区,且后续读取次数很少

为写密集型工作负载提供最佳性能

对于视频编码输出、数据采集记录、网络数据包组装等场景,主要操作是连续写入。
Write-Allocate
 确保了这些顺序写入能够充分利用缓存,将多次总线事务合并,减少总线开销,实现最高的写入吞吐量。

保护缓存免受零星读取的污染

如果一个数据块主要是只写的,那么对其偶尔的读取操作就不值得为它分配一个长期的缓存席位。
Read-No-Allocate
 的建议确保了这些”过客”数据不会把更重要的常驻数据从缓存中挤出去。

享受 Write-Back 的性能优势


Write-Back
 模式本身就提供了最好的写入性能,因为写入只到缓存为止,不需要立即更新主内存。这进一步减少了内存带宽的消耗和访问延迟。

这种内存类型的诞生,源于对流数据生成大数据处理工作负载的深入分析。

视频编码和流媒体服务的需求

在视频直播、视频会议等应用中,编码器需要持续地将压缩后的视频数据写入输出缓冲区。

背景:这些数据一旦写入,通常很快就会被网络栈读取并发送出去,之后就不再需要。需要优化写入性能,同时避免这些短期数据污染缓存。

解决方案
Write-Back Write-Allocate
 完美契合这种需求。它优化了编码器的写入性能,同时确保这些流数据不会长期占用缓存。

科学数据采集和记录系统

在科学研究、工业监控等场景中,系统需要持续记录传感器数据到存储缓冲区。

背景:数据采集是连续写入的过程,而数据分析可能是离线进行的。需要最大化数据记录的吞吐量。

解决方案:使用 
Write-Back Write-Allocate
 可以确保数据采集过程不受内存带宽限制,同时避免采集数据影响系统中其他任务的缓存效率。

网络协议栈的优化

在网络服务器中,协议栈需要组装外发的数据包,这同样是一个顺序写入的过程。

背景:数据包一旦发送,就很少再被读取。需要优化数据包的组装性能。

解决方案
Write-Back Write-Allocate
 提供了最优的写入性能,同时通过不在读取时分配缓存行来保护缓存。

数据库事务日志的写入优化

数据库系统使用事务日志来保证数据一致性,这些日志主要是顺序写入的。

背景:事务日志的写入性能直接影响数据库的整体吞吐量。日志数据一旦写入并确认,就很少被读取(除非进行恢复)。

解决方案:将事务日志的内存区域标记为 
Write-Back Write-Allocate
,可以在保持数据持久性的同时,最大化日志写入的性能。

总结
Write-Back Write-Allocate
 是一种为写密集型、顺序访问、读少次的工作负载设计的精密优化工具。它将 Write-Back 模式的高性能潜力与智能的缓存分配建议相结合,在确保高频写入操作享受最佳性能的同时,保护缓存免受低频读取操作的污染。当你能准确识别出系统中的数据生成管道(如编码器输出、数据记录、日志写入)时,使用这种内存类型可以带来显著的系统性能提升,是构建高吞吐量数据采集和处理系统的关键优化手段之一。

Write-Back Read and Write-Allocate 

这种内存类型。这是 Write-Back 模式下最”积极”的缓存策略,也是性能潜力最高的配置之一。Write-Back Read and Write-Allocate 在基础行为上与其它 Write-Back 类型一致,但它采取了最激进的缓存分配建议:无论是读还是写,只要发生缓存未命中,都强烈建议分配缓存行。 这是为读写混合频繁的热点数据设计的终极优化方案。

让我们通过一个具体的场景来理解:

场景:一个高性能数据库的缓冲池或一个游戏引擎中的活动场景图。这些数据结构被频繁地读取(查询、遍历)和写入(更新、修改),是系统的核心性能关键路径。

现在,我们看这种内存类型如何工作:

基础行为(与 Write-Back No-Allocate 相同)

写响应可从中间点获得:写入时,缓存可以立即响应。

写事务不需要在最终目的地可见:数据可以长期停留在缓存中,只有必要时才写回主内存。

读数据可从缓存副本获得:如果数据在缓存中,读取速度极快。

事务可修改、可预取、可合并:系统可以进行所有性能优化。

核心差异:极积极的分配建议

对读事务的建议:推荐分配

行为:当发生读未命中时,缓存强烈建议分配新行。

举例:数据库查询引擎第一次访问某个表的一行数据。由于是 
Read-Allocate
,系统会在缓存中为该数据分配缓存行。当后续查询需要访问同一行或相邻行时,就可以直接从缓存中读取。

对写事务的建议:推荐分配

行为:当发生写未命中时,缓存同样强烈建议分配新行。

举例:应用程序更新数据库中的某条记录。如果该记录不在缓存中,系统会分配缓存行,然后将更新写入缓存。后续对该记录的读写操作都会命中缓存。

综合效果:这种”双分配”策略确保了热点数据一旦被触及(无论是读还是写),就会在缓存中建立据点,为所有后续访问提供极致加速。这最大限度地利用了缓存来减少访问延迟。

这种策略的核心目标是为系统中最重要、最常访问的读写混合型数据提供无与伦比的低延迟访问体验。

为读写混合型热点数据提供最大性能

对于数据库缓冲池、活动对象集合、频繁更新的元数据等,其访问模式是密集的读写混合。积极的分配策略确保了无论访问方向如何,都能享受到缓存带来的极致速度。

最大化缓存的投资回报率

这种策略的理念是:对于那些已经被识别出的、极其重要的核心数据,就应该毫无保留地使用缓存。既然这些数据的访问频率极高,那么为其分配缓存行就是最值得的投资。

充分利用 Write-Back 的性能优势


Write-Back
 模式本身就提供了最好的写入性能,因为写入只到缓存为止。结合积极的缓存分配,它可以实现接近理论极限的读写性能。

这种内存类型的诞生,源于对企业级应用实时系统高性能计算工作负载的深刻理解。

数据库管理系统的核心优化

现代数据库系统的性能极度依赖于缓冲池(Buffer Pool)的效率。缓冲池缓存了最常访问的表数据和索引。

背景:缓冲池中的数据既被频繁读取(查询),也被频繁写入(更新、插入)。需要一种能同时优化这两种操作的极致缓存策略。

解决方案
Write-Back Read and Write-Allocate
 成为了数据库缓冲池的理想选择,因为它能确保热点数据长期驻留在高速缓存中。

实时游戏引擎的数据管理

在游戏引擎中,活动场景中的对象(如玩家、NPC、动态物体)的状态数据在每帧中都被频繁读写。

背景:帧率稳定性至关重要,任何缓存未命中都可能导致帧时间波动。

解决方案:将这些核心游戏对象数据标记为 
Write-Back Read and Write-Allocate
,可以确保它们在渲染循环中始终保持在缓存中,提供可预测的高性能。

高频交易系统的需求

在金融交易系统中,订单簿、持仓数据等核心数据结构被极度频繁地更新和查询。

背景:微秒级的延迟差异就可能导致显著的交易优势或劣势。

解决方案:使用这种积极的缓存策略,可以确保关键交易数据始终在缓存中热待命,实现最低的访问延迟。

操作系统的内核数据结构

操作系统中的进程控制块、锁结构、调度队列等核心数据结构被所有系统调用频繁访问。

背景:系统整体响应速度依赖于这些核心数据结构的访问效率。

解决方案
Write-Back Read and Write-Allocate
 确保了这些关键数据结构在缓存中常驻,提升了整个系统的响应性。

总结
Write-Back Read and Write-Allocate
 是一种”全力以赴”的缓存策略,代表了 AXI 协议中缓存优化的最高境界。它不像其他类型那样小心翼翼地防止缓存污染,而是假设它所应用的内存区域是系统中无可争议的”VIP热点”,值得占用最宝贵的缓存资源。当你能明确识别出系统中的性能绝对关键路径上的读写混合型核心数据时,使用这种内存类型可以带来极致的性能提升。这是优化数据库核心、游戏引擎、交易系统等高性能应用的终极武器,但需要谨慎使用,因为如果误用于非热点数据,会造成严重的缓存污染和性能下降。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
苏苏的理想生活的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容