内存类型Write-through Write-allocate详解
好的,我们来深入解析 AXI 协议中一个相对特殊但设计精巧的内存属性:Write-Through Write-Allocate。
这个组合初看可能有些反直觉,因为 Write-Through 和 Write-Allocate 似乎追求的是两个相反的目标(一个强调立即写内存,一个强调用缓存缓冲写操作)。但正是这种组合,解决了特定场景下的性能瓶颈。
核心结论先行
- 是什么: 
Write-Through Write-Allocate是一种缓存策略。Write-Through指写入时同时更新缓存和主内存;Write-Allocate指在写未命中时,先将数据所在的内存块载入缓存,然后再执行写入。 - 目标: 优化对尚未被缓存的、需要强一致性的、并且后续很可能被读取的数据的首次写入性能。
 - 本质: 通过 
Write-Allocate将“写未命中”转化为“写命中”,从而使得后续操作能享受到缓存带来的速度优势,同时通过Write-Through维持数据的全局可见性。 
深入解析:拆解与组合
1. Write-Through (透写)
- 核心行为: 所有写入操作(无论命中与否)都必须最终更新到主内存。如果是缓存命中,则同时更新缓存和内存。
 - 设计目标: 强数据一致性。保证其他系统主设备(如DMA、其他CPU核心)能立即看到写入的结果。
 
2. Write-Allocate (写分配)
- 核心行为: 当发生写未命中时(要写入的数据不在缓存中),CPU 不会直接写入内存。而是会:
- 在缓存中分配一个新的缓存行。
 - 将整个缓存行的数据从主内存加载到缓存中(即使你只想写其中一个字)。
 - 然后,在缓存中更新目标位置的数据。
 
 - 设计目标: 利用空间局部性原理。假设如果你要写一个数据,你很可能会很快再次写入或读取它附近的数据。提前加载整个缓存线,为后续的访问提速。
 
3. Write-Through Write-Allocate 的组合效应
这个组合的关键在于处理 “写未命中” 的场景。让我们对比一下不同策略的处理流程:
场景: CPU 要执行一个 32 位写操作(STR)到地址 0x1000,但该地址所在的 64-byte 缓存行当前不在缓存中。
- 
采用 Write-Through No-Allocate:
- 数据 (
0x1000处的 4 个字节) 被直接写入主内存。 - 操作结束。缓存无变化。
 
- 结果: 写入完成,一致性得到保证。但如果 CPU 马上要读 
0x1004,会发生读未命中,又需要一次慢速的内存访问。 
 - 数据 (
 - 
采用 Write-Through Write-Allocate:
- Write-Allocate 触发: CPU 在缓存中分配一个缓存行,对应地址范围 
0x1000-0x103F。 - 缓存行填充: CPU 发起一个突发读(Burst Read)操作,将整个 
0x1000-0x103F的数据块从内存读入刚分配的缓存行。 - 缓存更新: CPU 在缓存中更新 
0x1000处的 4 个字节为新数据。 - Write-Through 触发: 由于是写命中且策略是 Write-Through,CPU 将这 4 个字节的新数据写入主内存。
 - 操作结束。缓存中 now 拥有了该内存区域的副本。
 
- 结果: 写入完成,一致性得到保证。此外,整个 
0x1000-0x103F区域的数据都被加载到了缓存中。如果 CPU 马上要读0x1004,会发生读命中,数据直接从高速缓存中返回。 
 - Write-Allocate 触发: CPU 在缓存中分配一个缓存行,对应地址范围 
 
这个组合的最终效果是:
- 牺牲了单次写入的延迟: 一次原本简单的直接写内存操作,变成了“读整个缓存行 + 写缓存 + 写内存”的更复杂操作,延迟更高。
 - 换取了后续访问的超高速度: 为后续对该内存区域的一系列读写操作铺平了道路,它们都能以缓存速度进行。
 - 始终保持一致性: 因为每一步都遵循 Write-Through 原则。
 
为什么需要它?—— 应用场景
这种策略适用于对一片新的、未缓存的内存区域进行初始化或密集的写后读操作。
- 
堆内存的初始化:
- 场景: 程序调用 
malloc()分配了一块新内存,然后立即用memset()或一个循环对其进行清零或初始化。这块内存之前未被访问过,肯定不在缓存中。 - 为什么用它:
- 第一次写入 
0x1000(Write-Through Write-Allocate)虽然慢,但它将整个缓存行载入了缓存。 - 接下来初始化 
0x1004, 0x1008, ... 0x103F的写入操作,全部变成缓存命中的 Write-Through 写入,速度极快。 - 如果没有 Write-Allocate,每一次对未缓存地址的写入都要直接访问内存,速度会慢得多。
 
 - 第一次写入 
 
 - 场景: 程序调用 
 - 
创建需要共享的数据结构:
- 场景: CPU 核心需要初始化一个数据结构(例如一个任务描述符),然后立即通知另一个核心或 DMA 控制器去读取它。
 - 为什么用它:
Write-Through: 保证了另一个设备读取时,能从内存中看到完全初始化好的数据。Write-Allocate: 在 CPU 核心初始化该结构时,由于缓存行的加载,后续的字段写入都在缓存中完成,加快了初始化速度。CPU 核心自身如果很快要读取该结构,也能从缓存中快速访问。
 
 - 
对非时间顺序访问的写入:
- 场景: 写入一个数组,但索引是跳跃的、非顺序的。
 - 为什么用它: Write-Allocate 加载的是整个缓存行。即使你只写一个字节,它也会把附件的几十个字节都拉进来。当你接下来跳跃到写入同一个缓存行内的另一个地址时,这次写入就是缓存命中的,速度非常快。
 
 
在 AXI 协议中的表示
在 AXI 协议中,这是通过 AWCACHE 信号实现的(因为 Write-Allocate 主要与写事务相关)。
AWCACHE[1](Cacheable): 1 (表示是 Cacheable 的)AWCACHE[3](Write-Allocate): 1 (表示 Enable Write-Allocate)AWCACHE[2](Read-Allocate): 通常为 0 (但也可以为1)AWCACHE[0](Bufferable): 通常为 0 (强调写入必须到达内存)
一个典型的 Write-Through Write-Allocate 配置是 AWCACHE = 4'b1110。
C=1: CacheableWA=1: Write-AllocateRA=0: No Read-Allocate (也可以是1,但WA=1是核心)B=0: Not Bufferable
对比总结
| 属性 | 写未命中行为 | 优点 | 缺点 | 适用场景 | 
|---|---|---|---|---|
| Write-Through Write-Allocate | 分配缓存行,读入数据,更新缓存,写透内存 | 加速后续访问,强一致性 | 首次写入延迟极高 | 初始化一块后续要频繁访问的共享内存 | 
| Write-Through No-Allocate | 不分配,直接写内存 | 避免缓存污染,强一致性,首次写入快 | 后续访问可能慢 | 写入后不再读取的共享数据(如帧缓冲区) | 
| Write-Back Write-Allocate | 分配缓存行,读入数据,只更新缓存 | 写入性能极高,加速后续访问 | 数据非强一致,风险高 | 处理器私有数据的初始化 | 
| Non-cacheable | 直接写内存 | 强一致,简单 | 所有访问都慢 | 设备寄存器 | 
总结
Write-Through Write-Allocate 是一种 “用一次性的性能代价换取长期收益” 的缓存策略。
- 它的设计哲学: “磨刀不误砍柴工”。宁愿在第一次写入时花费额外开销(“磨刀”——分配并加载缓存行),也要为后续大量的访问操作做好极速响应的准备(“砍柴”——缓存命中)。
 - 它的核心价值: 它是数据初始化阶段的优化利器,特别适用于那些初始化后立即被本CPU核心或其他设备频繁访问的共享数据区域。
 - 它的代价: 单次写入延迟高,并且占用总线带宽(因为需要先执行一个缓存行填充的读操作)。
 
简单来说,如果你知道一块内存马上就要被“热”起来,使用这个属性是值得的。如果你知道它只会被“冷”写一次,那么就应该避免使用它。
                    
                
                
            
        
浙公网安备 33010602011771号