跳转至

实验说明

注意事项

我们建议在 Linux 系统上进行实验。如果在实验环境上遇到困难,请及时联系助教。

真机实验涉及到实验环境问题,因此我们预留的时间很充足,建议大家尽早开始实验

对于苹果芯片 ARM CPU 的 Mac 电脑,无法在 MacOS 中得知 Cache 的详细参数,建议更换其他环境进行实验。我们在此提供如下方案:

  1. 自行前往东主楼的机房进行实验,使用这些电脑应该不需要预约,但是无法远程使用。

  2. 坚持在苹果上实验也是可行的,实验给分会以大家的工作量和思考为重,如果得到有价值的结果可以适当加分。

对于 Windows 用户,在一些 stackoverflow 的回答中,有推荐通过 docker,直接使用 host CPU 创建虚拟镜像的方式运行 Linux 获得参数的方法,但我们没进行过测试。Windows系统直接进行测试也是可行的,但文档中提供的部分指令和函数不可用。

获得自己 CPU 的 cache 参数

在 Linux 系统中,直接执行 lscpu 命令,可以获得 Cache 的部分参数,例子如下:

如图,这是一个 i7-10750H 的笔记本 CPU。该 CPU 一共有 6 个 独立的 L1 ICache 和 L1 DCache,总大小均为 192KB。同时,此 CPU 有 6 个独立的 L2 Cache,总大小为 1.5 MB。此 CPU 还有一个 L3 Cache 实例,容量为 12 MB。

这意味着该 CPU 的每个核心有 192KB / 6 = 32 KB 的 L1 ICache 和 L1 DCache,1.5MB / 6 = 256 KB 的 L2 Cache。

同时,进入系统的 /sys/devices/system/cpu/cpu0/cache 目录,可以获得 cache 的更多参数,如下图:

通过读取 ways_of_associativity 文件,获得了 Cache 的相联度为 8。

通过读取 coherency_line_size 文件,获得了 Cache Line Size 为 64B。

对于 MacOS 系统,我们无法通过正常方式获取 CPU 的 Cache 规格,可以通过 sysctl -a | grep cache 获取部分 Cache 信息,也有些第三方的逆向结果。对于 x86-64 CPU 的 Mac 电脑,可以先获取 CPU 型号,然后到网络上查阅 CPU 参数。

对于 Windows 系统,这里有些好用的工具可以获得 Cache 信息:

  1. https://www.cpuid.com/softwares/cpu-z.html

  2. https://learn.microsoft.com/zh-cn/sysinternals/downloads/coreinfo

在正式进行实验之前,请大家至少获得自己 CPU 的 Cache 大小,Cache Line Size 和相联度以进行后面的实验结果验证。

进程绑定

通常情况下,每个 CPU 拥有自己的 L1 Cache。因此在测试之前,最好把程序绑定在指定的 CPU 上,缓解调度带来的 L1 DCache Miss。

参考方法:

#define _GNU_SOURCE
int main() {
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(3, &mask);
    if (sched_setaffinity(0, sizeof(mask), &mask) < 0) {
        perror("sched_setaffinity");
    }
    // More code follows
}

计时方式

单次访存的延迟太小,所以需要每次实验有足够的访存次数。c++ 包含多种不同精度的计时方式,例如 clock() 和更高精度的 clock_gettime()。

#include <time.h>
int main () {
   clock_t start = clock();
   // code
   clock_t end = clock();
   double duration = (double)(end - start) / CLOCKS_PER_SEC;
}
#include <time.h>
int main () {
   struct timespec start, end;
   clock_gettime(CLOCK_MONOTONIC, &start);
   // code
   clock_gettime(CLOCK_MONOTONIC, &end);
   double duration = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
}

访存相关

x86-64 架构的处理器的 CPU 和 L1 Cache 之间还存在一些 Buffer 优化访存操作。其中 Store 操作收到的影响要远小于 Load 操作受到的影响。所以建议访存时使用 Store 操作。

(c++17 之前)对于需要计时的代码段内部的中间变量,建议使用 register 关键字将其与寄存器绑定。但注意 register 关键字是一种推荐,而不是强制。同时 x86-64 架构的通用寄存器数量不算多。

如果希望使用 load 操作,但担心被编译器优化,可以使用内联汇编的方式,也可以尝试使用 volatile 关键字。

预取器相关

数据预取器是 CPU 中用于预测未来内存访问模式并提前加载数据的硬件模块,旨在减少 Cache Miss 带来的延迟。部分数据预取器可以识别顺序访存或按照一定规律访存的序列,进而干扰试验结果,此时需要修改实验设计。

已知苹果 M1 - M4 会对顺序实验产生影响,intel 截至 14 代不存在影响,其余处理器暂不确定。

TLB 相关

实验过程中 TLB miss 会在一定程度上影响测量结果。可以使用大页映射降低干扰。

开启过程如下:

  1. 查看内核对大页的支持

    查看内核配置文件 /proc/config.gz 中 CONFIG_HUGETLB_PAGE 和 CONFIG_HUGETLBFS 是否开启。

  2. 配置大页

    给某个根目录配置大页,使该目录下的文件操作都使用 2MiB 大页(xxx 为某个目录名,不是文件名。需要在 root 权限下完成操作)。

    mkdir /mnt/xxx
    mount none /mnt/xxx -t hugetlbfs
    
  3. 分配空闲大页

    XX 为分配的大页数量。需要在root权限下完成操作。

    echo XX > /proc/sys/vm/nr_hugepages
    
  4. 代码中使用内存映射替代内存分配操作

    int fd = open("/mnt/wsl/huge/temp", O_CREAT | O_RDWR, 0755);
    if (fd < 0) {
        perror("cannot open huge page");
    }
    
    int *array = (int*) mmap(0, ALLOCATE_SIZE * sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    
    // use array in the following codes
    

最后更新: 2025年3月12日
作者:cuibst (50.0%), Zheng Hongpei (26.06%), Zheng Hongpei (23.94%)