3

我想知道是否有任何单个事件可以捕获 L1D 缓存未命中。我试图通过测量延迟以在开始时使用 rdtsc 访问特定内存来捕获 L1d 缓存未命中。在我的设置中,如果发生 L1d 缓存未命中,它应该会命中 L2 缓存。因此,我用 RDTSC 测量访问内存的延迟,并将其与 L1 缓存延迟和 L2 缓存延迟进行比较。但是,由于噪音,我无法辨别它是击中 L1 还是 L2。所以我决定使用 RDPMC。

我发现有几个 API 提供了一些功能来轻松监控 perf 事件,但我想直接在我的测试程序上使用 RDPMC 指令。我发现 MEM_INST_RETIRED.ALL_LOADS-MEM_LOAD_RETIRED.L1_HIT 可用于计算 L1D 中未命中的已退休加载指令的数量。(使用 PAPI_read_counters 计算 L1 缓存未命中会产生意外结果)。但是,这篇帖子似乎在谈论 papi Api。

在执行 rdpmc 指令以捕获特定事件之前,如何找到应该为 ecx 寄存器分配哪些值?另外,我想知道是否有任何单个事件可以告诉我在两条 rdpmc 指令之间的一条内存加载指令发生 L1 未命中,如下所示。

c = XXX; //I don't know what value should be assigned for what perf counter..
asm volatile(
    "lfence"
    "rdpmc" 
    "lfence"
    "mov (0xdeadbeef), %%r10"//read memory
    "mov %%eax, %%r10        //read lower 32 bits of counter
    "lfence"                
    "rdpmc"                  //another rdpmc to capture difference
    "sub %%r10, %%eax        //sub two counter to get difference
    :"=a"(a)
    :"c"(c)
    :"r10", "edx");

在此处输入图像描述

我目前正在使用 9900k 咖啡湖机,所以我在英特尔手册中搜索了咖啡湖机的 perf counter number。似乎只在加载指令之前和之后捕获两个 MEM_LOAD_RETIRED.L1_HIT 就足以捕获事件,但我不确定这样做是否可以。我也不知道如何将该 perf 事件编码为ecx 寄存器。

最后,我想知道 rdpmc 指令背靠背是否需要任何序列化指令。就我而言,因为我只放置了加载指令并测量 L1d 缓存未命中是否发生,所以我将第一条 rdpmc 指令与 lfence 指令括起来,并在最后一条 rdpmc 之前再放置一条 lfence 指令,以确保加载指令在第二条 rdpmc 之前完成。

添加代码

asm volatile (                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "rdpmc\n\t"                                                                                                                                                                                                                                                                               
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "mov %%eax, %%esi\n\t"                                                                                                                                                                                                                                                                    
        //measure                                                                                                                                                                                                                                                                                 
        "mov (%4), %%r10\n\t"                                                                                                                                                                                                                                                                     
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "rdpmc\n\t"                                                                                                                                                                                                                                                                               
        "lfence\n\t"                                                                                                                                                                                                                                                                              
        "sub %%esi, %%eax\n\t"                                                                                                                                                                                                                                                                    
        "mov %%eax, (%0)\n\t"
        :
        :"r"(&perf[1]), "r"(&perf[2]), "r"(&perf[3]),                                                                                                                                                                                                                                              
         "r"(myAddr),   "c"(0x0)                                                                                                                                                                                                         
        :"eax","edx","esi","r10", "memory");

我还用 isolcpu 固定了我的 3 号核心,并禁用了超线程进行测试。MSR 寄存器已用以下命令计算

    sudo wrmsr -p 3 0x186 0x4108D1 #L1 MISS
4

1 回答 1

2

有一个 rdpmc 使用示例:https ://github.com/jdmccalpin/low-overhead-timers by John https://stackoverflow.com/a/60267195 ( http://sites.utexas.edu/jdm4372/2018 /07/23/comments-on-timing-short-code-sections-on-intel-processors/)。

还提到了准备使用的工具来测量指令:https ://arxiv.org/pdf/1911.03282.pdf https://github.com/andreas-abel/nanoBench

这个答案https://stackoverflow.com/a/60267531有使用 perf_event_open 设置事件计数器和 rdpmc 读取计数器的示例。

根据https://www.felixcloutier.com/x86/rdpmc,rdpmc不是序列化的,也不是两个未序列化的 rdpmcs 之间的单调:

RDPMC 指令不是序列化指令;也就是说,这并不意味着前面的指令引起的所有事件都已经完成,或者后面的指令引起的事件还没有开始。如果需要准确的事件计数,软件必须在 RDPMC 指令之前和/或之后插入序列化指令(例如 CPUID 指令)。

不保证执行背靠背快速读取是单调的。为了保证背靠背读取的单调性,必须在两条 RDPMC 指令之间放置一条序列化指令。

jevents 库可用于生成 PMC 事件选择器:https ://github.com/andikleen/pmu-tools/tree/master/jevents 。最新版本的 perf linux 分析工具在内部使用它。jevents 也有简单的 api 来使用 rdpmc 命令

if (rdpmc_open(PERF_COUNT_HW_CPU_CYCLES, &ctx) < 0) ... error ...
start = rdpmc_read(&ctx);
... your workload ...
end = rdpmc_read(&ctx);

libpfm4 的 showevtinfo 可能会生成与 rdpmc 的 ecx 格式兼容的事件 id,但我不确定:https ://stackoverflow.com/a/46370111

使用 nanobench,我们可以检查 Skylake 事件的源代码: https ://github.com/andreas-abel/nanoBench/blob/master/configs/cfg_Skylake_common.txt

D1.01 MEM_LOAD_RETIRED.L1_HIT
D1.08 MEM_LOAD_RETIRED.L1_MISS
D1.02 MEM_LOAD_RETIRED.L2_HIT
D1.10 MEM_LOAD_RETIRED.L2_MISS
D1.04 MEM_LOAD_RETIRED.L3_HIT
D1.20 MEM_LOAD_RETIRED.L3_MISS

在https://github.com/andreas-abel/nanoBench/blob/master/common/nanoBench.c 中解析parse_counter_configs()pfc_configs[n_pfc_configs].evt_numdot pfc_configs[n_pfc_configs].umask;编码configure_perf_ctrs_programmable

        uint64_t perfevtselx = read_msr(MSR_IA32_PERFEVTSEL0+i);
        perfevtselx &= ~(((uint64_t)1 << 32) - 1);

        perfevtselx |= ((config.cmask & 0xFF) << 24);
        perfevtselx |= (config.inv << 23);
        perfevtselx |= (1ULL << 22);
        perfevtselx |= (config.any << 21);
        perfevtselx |= (config.edge << 18);
        perfevtselx |= (os << 17);
        perfevtselx |= (usr << 16);

        perfevtselx |= ((config.umask & 0xFF) << 8);
        perfevtselx |= (config.evt_num & 0xFF);

        write_msr(MSR_IA32_PERFEVTSEL0+i, perfevtselx);

因此,写入 IA32_PERF_EVTSELx MSR 的寄存器值的两个低字节是 evt_num 和 umask。不确定它是如何翻译成 rdpmc ecx 格式的。

John 说 rdpmc 命令需要“24-40 个周期范围内的东西”,并描述“英特尔架构无法以低延迟/开销从用户空间更改性能计数器事件选择编程”。 https://community.intel.com/t5/Software-Tuning-Performance/Capturing-multiple-events-simultaneously-using-RDPMC-instruction/td-p/1097868

rdpmc 的文档说同样的https://www.felixcloutier.com/x86/rdpmc

ECX 寄存器指定计数器类型(如果处理器支持架构性能监控)和计数器索引。通用或专用性能计数器由 ECX[30] = 0 指定

ECX 不包含要计数的确切事件,而是计数器的索引。有 2、4 或 8 个“可编程性能计数器”,您必须首先使用 wrmsr(在内核模式下)设置一些计数器,例如使用 MSR IA32_PERF_EVTSEL0 设置索引为 0 的计数器,然后使用 rdpmc 和 ecx[30] =0 和 ecx[29:0]=0; 使用 MSR IA32_PERF_EVTSEL3 使用 rdpmc,ecx[30]=0 和 ecx[29:0]=3。

我认为使用 PAPI API 设置计数器并在测试代码之前和之后从中获取读数会更容易。但是 API 调用会增加开销,因此您的测试代码应设计为重复要测试的序列数次(数千次或更多次)。默认情况下,通过 CR4 中的 PCE 标志禁用用户空间代码的 perfcounters rdpmc/rdmsr - https://www.felixcloutier.com/x86/rdpmc ( echo 2 > /sys/bus/event_source/devices/cpu/rdpmc);仅启用 linux 内核访问。并且用于设置计数器的 wrmsr 也被禁用。

有几种已知方法可以在不使用 perfcounter 的情况下测量缓存层次结构延迟:https : //www.7-cpu.com/utils.html 和 lmbench/src/lat_mem_rd.c,但要获得实际的缓存延迟,需要进行一些手动后处理.

于 2020-10-08T14:35:23.980 回答