为何Perf在Zen 2平台无法打开L1缓存写访问事件?
在AMD Zen2架构的Ryzen Threadripper 3960X 24核处理器上,尝试通过perf_event_open读取以下L1d缓存事件:
- L1d所有读访问
- L1d所有写访问
- L1d读缺失
- L1d写缺失
按照perf_event_open的规则,通过位运算构造perf_event_attr.config:
config = (perf_hw_cache_id) | (perf_hw_cache_op_id << 8) | (perf_hw_cache_op_result_id << 16);
测试L1D_read_access时代码正常运行,但测试L1D_write_access时,perf_event_open返回-1,报错Error: Perf couldn't open event.。该代码在Intel平台可正常执行。
机器信息
$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 43 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 24 On-line CPU(s) list: 0-23 Vendor ID: AuthenticAMD Model name: AMD Ryzen Threadripper 3960X 24-Core Processor CPU family: 23 Model: 49 ... $ uname -r 6.13.2-arch1-1
说明
代码中设置了PERF_FORMAT_ID和PERF_FORMAT_GROUP标志,用于后续创建包含四个目标事件的事件组。
测试代码
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <linux/perf_event.h> #include <sys/syscall.h> #include <unistd.h> #include <sys/ioctl.h> #define MAX_EVENTS 4 struct read_format{ uint64_t nr; struct { uint64_t val; uint64_t id; }rec[MAX_EVENTS]; }; int main(){ // define HW_CACHE event uint64_t L1D_write_access = ((PERF_COUNT_HW_CACHE_L1D) | \ (PERF_COUNT_HW_CACHE_OP_WRITE << 8) | \ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)); uint64_t L1D_read_access = ((PERF_COUNT_HW_CACHE_L1D) | \ (PERF_COUNT_HW_CACHE_OP_READ << 8) | \ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)); uint64_t test_event = L1D_write_access; // set event attributes struct perf_event_attr pea; memset(&pea, 0, sizeof(pea)); pea.size = sizeof(struct perf_event_attr); pea.type = PERF_TYPE_HW_CACHE; pea.config = test_event; pea.read_format = PERF_FORMAT_ID | PERF_FORMAT_GROUP; pea.disabled = 1; pea.exclude_kernel = 1; pea.exclude_hv = 1; pea.exclude_idle = 1; // open event int64_t event_fd = -1; event_fd = syscall(SYS_perf_event_open, &pea, 0, -1, event_fd, 0); if (event_fd == -1) { fprintf(stderr, "Error: Perf couldn't open event.\n"); exit(EXIT_FAILURE); } uint64_t event_id; ioctl(event_fd, PERF_EVENT_IOC_ID, &event_id); ioctl(event_fd, PERF_EVENT_IOC_RESET, 0); printf("Created event ID: %lX\n", event_id); // measure work size_t mem_l = 1024*1024; uint64_t var = 0; uint64_t *mem = (uint64_t*)malloc(mem_l*sizeof(uint64_t)); ioctl(event_fd, PERF_EVENT_IOC_ENABLE, 0); for(size_t i=0; i<mem_l; i++){ mem[i] ^= var; } ioctl(event_fd, PERF_EVENT_IOC_DISABLE, 0); // read results struct read_format rfmt; ssize_t read_bytes = read(event_fd, &rfmt, sizeof(rfmt)); if(!read_bytes){ fprintf(stderr, "Error: Zero bytes read from the file descriptor.\n"); exit(1); } if(read_bytes == -1){ perror("Error: Couldn't read the event's file descriptor"); exit(1); } printf("%ld bytes read from events file.\n", read_bytes); for(size_t i=0; i<rfmt.nr; i++){ printf("\nregister %lu:\n", i); printf("%12lu : val\n", rfmt.rec[i].val); printf("%12lX : id\n", rfmt.rec[i].id); } printf("done\n"); }
原因
AMD Zen2架构的PMU(性能监控单元)不支持PERF_COUNT_HW_CACHE_OP_WRITE + PERF_COUNT_HW_CACHE_RESULT_ACCESS的组合事件。Intel与AMD的PMU事件模型存在差异,部分通用的PERF_TYPE_HW_CACHE抽象事件在AMD平台没有对应的硬件计数器支持。
对于Zen2架构,L1D写访问无法通过标准的PERF_TYPE_HW_CACHE事件直接获取,必须使用AMD特定的原始事件代码。
解决方法
方法1:替换为AMD原始PMU事件
Zen2架构中,L1D写访问对应的原始事件配置为:
- 事件选择码:
0x40 - UMASK:
0x02
修改代码中的事件定义和类型:
// Zen2 L1D写访问的原始事件 uint64_t L1D_write_access_raw = (0x02 << 8) | 0x40; uint64_t test_event = L1D_write_access_raw; // 修改事件类型为原始事件 pea.type = PERF_TYPE_RAW;
方法2:通过perf list确认支持的事件
先通过命令查看Zen2平台支持的L1D相关事件:
perf list | grep L1-dcache
找到对应的写访问事件(如l1d_writes),通过perf stat -e l1d_writes ./your_program验证后,再转换为perf_event_open的配置。
补充:L1D读/写缺失的处理
同样需要使用AMD原始事件:
- L1D读缺失:
(0x01 << 8) | 0x41 - L1D写缺失:
(0x02 << 8) | 0x41
验证
修改代码后重新编译运行,即可正常打开L1D写访问事件并获取计数。
内容的提问来源于stack exchange,提问作者onlycparra

