跳转到内容
唯一赫兹
返回

操作系统 - 硬件结构

CPU 缓存

CPU 缓存层级

# index0 和 index1 是 L1 缓存,包含数据缓存和指令缓存
$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data

$ cat /sys/devices/system/cpu/cpu0/cache/index1/type
Instruction

# L2 和 L3 是统一缓存,L1 和 L2 是核心独有的
$ cat /sys/devices/system/cpu/cpu0/cache/index2/type
Unified

# L3 缓存是多核心共享的
$ cat /sys/devices/system/cpu/cpu0/cache/index3/type
Unified

CPU Cache 的数据结构和读取过程

Cache Line 是 CPU 从缓存读取数据的最小单位

# 这说明 Cache Line 大小是 64 字节
$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64

Cache Line 包含以下部分

内存地址包含三个部分

当 CPU 需要根据内存地址读取一个内存块时,步骤如下:

  1. 根据索引定位缓存行
  2. 检查有效位
  3. 对比缓存行的组标记和内存地址的组标记
  4. 缓存行有效,且组标记相等,说明缓存命中,根据偏移量,从缓存块读取对应的数据

缓存行-内存块的映射策略

取模技巧

取模时可以利用公式 x % 2^n = x & (2^n - 1)

提高缓存命中率

缓存一致性

CPU 更新数据的策略

写回策略会导致不同核心的缓存不一致,各自对缓存修改并写回有不确定性 要解决缓存不一致问题要做到两点:

  1. 写传播:当一个核心修改 Cache 中的变量后,要传播到其他核心的 Cache
  2. 串行化:其他核心看到的数据变化顺序必须是一致的

MESI 协议

MESI 协议通过以下两个技术实现缓存一致性

CPU 任务执行机制

伪共享问题

如果两个核心各有一个线程,两个线程分别需要对一个变量进行频繁修改,这两个变量如果在同一个缓存行中,会导致缓存中一个变量修改就会让另一个变量失效(见 MESI 协议),缓存命中率会大幅降低 避免伪共享的方法:

  1. linux 内核中有一个 __cacheline_aligned_in_smp 宏定义,用于解决伪共享问题,这个宏定义会将变量的地址设置为缓存行对齐地址
struct {
    int a;
    int b;
}

struct {
    int a;
    int b __cacheline_aligned_in_smp;
}

abstract class RingBufferPad {
    protected long p1, p2, p3, p4, p5, p6, p7;
}

abstract class RingBufferFields<E> extends RingBufferPad {
    // 其他字段...
    private final long indexMask;
    private final Object[] entries;
    protected final int bufferSize;
    protected final Sequencer sequencer;

    // 其他方法...
}

public final class RingBuffer<E> extends RingBufferFields<E>
        implements Cursor, EventSequencer<E>, EventSink<E> {

    public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE;

    protected long p1, p2, p3, p4, p5, p6, p7;

    // 其他字段和方法...
}

由于「前后」各填充了 7 个不会被读写的 long 类型变量,所以无论怎么加载 Cache Line,这整个 Cache Line 里都没有会发生更新操作的数据,只要数据被频繁访问,就一直不会失效

线程选择问题



上一篇
操作系统 - 内存管理
下一篇
Kafka 入门