Reference:
- Cache 技术实战分析
- Performance Analysis and Tuning on Modern CPU, 4.10 内存延迟和带宽
测量方法:
Intel MLC
- MLC 可以使用不同的访问模式和负载来测量缓存和内存的延迟和带宽
延迟测量
- MLC 通过进行相关加载(也称为指针追 踪)来测量空闲延迟。一个测量线程分配一个非常大的缓冲区,并对其进行初始化,以便缓冲区内的每个(64 字节) 缓存行包含指向该缓冲区内另一个非相邻缓存行的指针。通过适当调整缓冲区的大小,我们可以确保几乎所有的加 载都命中某个级别的缓存或主存。
简易使用:
1 | ./mlc --idle_latency -c0 -L -b10m |
--idle_latency: 测量读取延迟而不加载系统- 而另一个
--loaded_latency选项, 用于在由其他线程生成的内存流量存在时测量延迟
- 而另一个
-c0将测量线程固定在逻辑 CPU 0 上-L启用大页以限制测量中的 TLB 影响- 如果操作系统中没有足够的大页,可以不使用该选项,但 TLB 影响就无法避免
-b10m告诉 MLC 使用 10MB 缓冲区- 使用不同的缓冲区大小扫描可以得知系统的多级 cache 访问延时和不同级别的 cache 大小
带宽测量
MLC 使用不同的读写比例来测量带宽
1 | ./mlc --max_bandwidth -k0-15 -Y -L -b10m |
-k指定了用于测量的 CPU 编号列表-Y选项告诉 MLC 使用 AVX2 加载, 即每次加载 32 字节
自制简单 Benchmark 程序
访存读取延时测量一般会分配一个较大的 buffer ,遍历整个 buffer 以使 cache 被填满从而触发 cache miss 并访问下一级 cache。
采用指针追踪的方式(可以避免编译器优化掉访存指令),即访问 buffer 的每一项中都保存着下一次访问的 buffer 的索引,下一次的访问将以当前 buffer 中读出之后的数据作为地址继续访问。
代码实现框架包含两步:
申请一个 buffer,buffer size 需要大于 upper level cache size,小于等于当前 level cache size
- 第一次遍历进行预热,将数据全部加载到 cache 中
- 数据就是下一次访问的地址 (指针追逐)
- 第二次遍历统计耗时,计算每次 read 的延迟平均值
- 通过宏展开使得编译多条实际的访存指令,避免分支指令和循环计算指令的执行对于访存延时的统计产生影响
1 | ///============================== |
访存步幅的影响
访存步幅如果以 8 字节为粒度,顺序读取 128KB 的 buffer,假设数据命中的是 L2,那么数据就会被缓存到 L1,一个 cache line 其他的访存操作都只会命中 L1,从而导致测量的 L2 延迟明显偏小。
因此访存步幅应该以 cache line size 为粒度
硬件预取的影响
- 通过简单地将访存步幅再提高一点,可以避免一部分预取的影响
- 另一种更为通用的做法是保存指针时随机化(并限制到 buffer 内)
1 | for (i = 0; i < size; i++) |
TLB miss 的影响
如果 buffer size 较大时可能导致 TLB miss 对延时也造成一定影响,可以通过使用大页来减轻影响。
1 | int mem_flag = MAP_PRIVATE | MAP_ANON | MAP_HUGETLB; |