CPU 会读取“包含当前访问地址的那一整条 Cache Line”。这条 Cache Line 是按固定大小、固定边界对齐划分的。假设 Cache Line 大小是64 Byte。内存不是这样按你访问的位置临时划分访问地址 ↓ [ 当前地址 ][ 后面 63 Byte ]而是早就按 64 Byte 对齐切好了0x1000 ~ 0x103F 第 1 条 Cache Line 0x1040 ~ 0x107F 第 2 条 Cache Line 0x1080 ~ 0x10BF 第 3 条 Cache Line所以如果 CPU 访问地址 0x1010它会把整条0x1000 ~ 0x103F加载进 Cache。这时相对于0x1010来说确实既包含前面的地址0x1000 ~ 0x100F 后面的地址0x1011 ~ 0x103F所以答案是如果访问地址在一条 Cache Line 中间那么会同时把它前面和后面的数据都读进来。核心规则按对齐边界读取对于 64 Byte 的 Cache LineCache Line 起始地址 访问地址 ~(64 - 1)也就是把地址的低 6 位清零。因为64 2^6所以地址最低 6 位表示在 Cache Line 内部的偏移。例如访问地址0x101C Cache Line 大小64 Byte那么0x101C 所在的 Cache Line 起始地址 0x1000 结束地址 0x103F所以读取范围是0x1000 ~ 0x103F不是0x101C ~ 0x105B地址如何分配到 Cache 里一个内存地址通常被拆成三部分Tag | Index | Offset以 64 Byte Cache Line 为例Offset 6 bit因为一条 Cache Line 内有 64 个字节需要 6 位来定位其中某个字节。含义如下部分作用Offset选择 Cache Line 内的具体字节Index决定放到 Cache 的哪一组Tag判断 Cache 中的数据是不是目标内存块举个简单例子。假设Cache Line 大小 64 Byte Cache 一共有 128 组那么Offset 6 bit Index 7 bit Tag 剩下的高位地址结构大概是高位 低位 [ Tag ][ Index ][ Offset ]访问一个地址时CPU 会1. 用 Index 找到 Cache 中对应的一组 2. 在这一组里比较 Tag 3. 如果 Tag 匹配说明 Cache Hit 4. 如果不匹配说明 Cache Miss 5. 从内存中读取包含该地址的整条 Cache Line 6. 放入 Index 指定的那一组如果那一组已经满了怎么办如果是组相联 Cache比如 4 路组相联Set 5: Way 0: 一条 Cache Line Way 1: 一条 Cache Line Way 2: 一条 Cache Line Way 3: 一条 Cache Line如果这 4 个位置都满了又有新的 Cache Line 要放进来CPU 就要替换掉其中一个。常见替换策略有LRU / 近似 LRU / 随机 / 伪随机简单理解就是把最近不常用的一条 Cache Line 挤出去。特殊情况一次访问跨越两条 Cache Line如果访问的数据刚好跨了 Cache Line 边界就可能读取两条 Cache Line。比如 Cache Line 是0x1000 ~ 0x103F 0x1040 ~ 0x107F如果 CPU 要读取 8 Byte 数据起始地址是0x103C那么它需要的数据范围是0x103C ~ 0x1043这个范围跨过了边界所以可能需要加载0x1000 ~ 0x103F 0x1040 ~ 0x107F也就是两条 Cache Line。还要区分 Cache Line 读取和预取正常的 Cache Miss 读取的是包含当前地址的那一条 Cache Line但是现代 CPU 还有硬件预取器。如果 CPU 发现你在连续访问arr[0],arr[1],arr[2],arr[3]...它可能提前把后面的 Cache Line 也读进来当前 Cache Line 下一条 Cache Line 再下一条 Cache Line这叫prefetch预取。所以Cache Line 读取读取当前地址所在的那一整条 Cache Line 硬件预取可能额外读取后面甚至附近的 Cache Line一句话总结CPU 按 Cache Line 读取时不是从当前物理地址开始往后读而是读取当前地址所在的“对齐 Cache Line 区间”。所以如果当前地址在这条线中间那么它前面的地址和后面的地址都会一起被读入 Cache。