You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

perf_event_open场景下mmap()是否比read()更快及PMU访问问题

关于PMU性能监控的两个问题

我需要在应用中监控硬件、软件及硬件缓存类事件,性能是这类profiling应用的核心需求。理想情况下我希望通过MRS指令直接读取CPU PMU寄存器获取周期计数等数据,但内核默认禁用EL0级别的PMU访问,因此只能借助perf工具。目前我的read_values()函数使用read()读取事件结果,为提升性能,参考PMU硬件计数器访问文档后提出两个问题:

  1. 使用mmap()替代read()从文件描述符读取事件值是否能提升性能?若可以该如何实现?
  2. 是否可以通过MRS指令直接读取PMU寄存器?文档显示可行,但找不到相关实现示例。

我尝试将read_values()中的read()替换为mmap(),但映射后读取到的事件数量nr为0,无法获取有效事件值。相关代码如下:

原始代码

struct read_format {
  uint64_t nr;          /* The number of events */
  struct {
    uint64_t value;     /* The value of the event */
    uint64_t id;        /* if PERF_FORMAT_ID */
  } values[nr];
};

int main() {
  struct perf_event_attr attr1;
  attr1.type = PERF_TYPE_HARDWARE;
  attr1.config = PERF_COUNT_HW_CPU_CYCLES;
  attr1.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
  int main_fd = syscall(__NR_perf_event_open, &attr1, 0, -1, -1, 0);
  uint64_t id1;
  ioctl(main_fd, PERF_EVENT_IOC_ID, &id1);
  ioctl(main_fd, PERF_EVENT_IOC_RESET, 0);
  ioctl(main_fd, PERF_EVENT_IOC_ENABLE, 0);

  struct perf_event_attr attr2;
  attr2.type = PERF_TYPE_HARDWARE;
  attr2.config = PERF_COUNT_HW_CACHE_REFERENCES;
  attr2.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
  int fd2 = syscall(__NR_perf_event_open, &attr2, 0, -1, main_fd, 0);
  uint64_t id2;
  ioctl(fd2, PERF_EVENT_IOC_ID, &id2);
  ioctl(fd2, PERF_EVENT_IOC_RESET, 0);
  ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0);

  // read_values and log "START"

  // action

  // read_values and log "END"

  return 0;
}

read_values() {
  char buffer[4096];
  int read_bytes = read(main_fd, &buffer, sizeof(buffer));
  if (read_bytes == -1) { return 1; }

  struct read_format* rf = (struct read_format*) buffer;
  int values[rf->nr];
  for (int i=0; i<rf->nr; i++) {
    values[i] = rf->values[i].value;
  }
}

尝试修改后的read_values()

read_values() {
  char* buffer = mmap(NULL,  sizeof(read_format), PROT_READ, MAP_SHARED, main_fd, 0);
  if (buffer == MAP_FAILED) { return 1; }

  struct read_format* rf = (struct read_format*) buffer;
  if (rf->nr == 0) { return 1; }
  int values[rf->nr];
  for (int i=0; i<rf->nr; i++) {
    values[i] = rf->values[i].value;
  }
}

问题解答

1. 使用mmap()替代read()的可行性与正确实现

用mmap()确实能提升性能,原因是read()每次调用都会陷入内核拷贝数据,而mmap()是将内核空间的计数器数据直接映射到用户空间,避免了数据拷贝,适合高频读取场景。

你当前的问题出在映射的大小和使用方式上:

  • sizeof(read_format)是错误的,因为这个结构体包含柔性数组,实际大小取决于事件数量,正确的映射大小应该是PAGE_SIZE(通常4KB),perf工具的映射接口默认使用整页大小。
  • 不能每次调用read_values()都重新mmap(),应该在初始化阶段完成一次映射,后续直接读取映射区域即可,频繁mmap()/munmap()反而会带来性能开销。

正确的实现步骤:

  1. 在main()中打开perf事件后,立即对主事件fd执行mmap():
#define PAGE_SIZE 4096
char* perf_map = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, main_fd, 0);
if (perf_map == MAP_FAILED) {
    // 错误处理
}
  1. 修改read_values()直接读取映射区域,注意先触发数据更新:
void read_values(char* perf_map, int main_fd) {
    // 先触发内核更新用户空间的映射数据
    ioctl(main_fd, PERF_EVENT_IOC_REFRESH, 1);
    struct read_format* rf = (struct read_format*)perf_map;
    if (rf->nr == 0) {
        // 错误处理
        return;
    }
    uint64_t values[rf->nr]; // 用uint64_t避免计数器值截断
    for (int i=0; i<rf->nr; i++) {
        values[i] = rf->values[i].value;
    }
    // 处理values数据
}
  1. 程序结束前调用munmap(perf_map, PAGE_SIZE)释放映射。

2. 通过MRS指令直接读取PMU寄存器

可以直接用MRS指令读取,但需要满足几个条件:

  • ARM64架构:内核需开启CONFIG_ARM64_PMU_USER_ACCESS=y配置,并且运行前设置系统参数:
    echo 0 > /proc/sys/kernel/perf_event_paranoid
    echo 1 > /sys/devices/system/cpu/pmu/userspace_access
    
  • x86架构:需将perf_event_paranoid设为-1,内核支持用户空间RDPMC指令。

ARM64下读取周期计数器的简单实现示例:

#include <stdint.h>
#include <stdio.h>

// 读取PMU周期计数器(PMCCNTR_EL0寄存器)
static inline uint64_t read_pmu_cycle() {
    uint64_t val;
    asm volatile("mrs %0, pmccntr_el0" : "=r"(val));
    return val;
}

int main() {
    uint64_t start = read_pmu_cycle();
    // 执行需要监控的操作
    for (int i=0; i<1000000; i++);
    uint64_t end = read_pmu_cycle();
    printf("Cycles: %lu\n", end - start);
    return 0;
}

注意:如果要监控其他事件(如缓存访问),需要先通过PMU配置寄存器(如PMCR_EL0、PMSELR_EL0)选择对应事件,具体配置需参考对应架构的官方参考手册。

内容的提问来源于stack exchange,提问作者Kron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 03:54:56