Apple M2架构下读取ARM TSC(PMCCNTR_EL0)出现异常值的问题
问题原因分析
你遇到的问题本质是Apple M2(Apple Silicon)对PMCCNTR_EL0寄存器的访问限制,以及计数器未正确启用导致的无效值:
- Apple Silicon的性能监控单元(PMU)默认处于禁用状态,且用户态程序没有访问PMCCNTR_EL0的权限,直接读取会得到无意义的随机值或未初始化的寄存器内容。
- 你看到的超大数值是
uint64_t类型的溢出结果:当after的值小于before时,无符号整数相减会产生溢出,得到一个接近UINT64_MAX的数。
正确解决方案(Apple Silicon 读取核心周期计数器)
要在Apple M2上精准测量核心时钟周期数,需要先启用PMU的用户态访问权限,并正确配置计数器,步骤如下:
1. 启用用户态PMU访问(需要root权限)
macOS默认禁止用户态访问PMU,需要通过系统配置解除限制:
- 重启Mac,按住
Command + R进入恢复模式 - 打开终端,执行:
csrutil enable --without pmu - 重启回到正常系统
2. 正确初始化PMCCNTR_EL0计数器
在读取计数器前,需要初始化PMU,确保计数器以核心时钟频率递增:
#include <cstdint> #include <iostream> // 初始化PMU,启用PMCCNTR_EL0计数器 void InitPMU() { uint64_t pmcr; // 读取PMCR_EL0寄存器,配置计数器 asm volatile("mrs %0, PMCR_EL0" : "=r"(pmcr)); // 置位P位(启用计数器)、C位(重置计数器)、D位(禁止分频) pmcr |= (1 << 0) | (1 << 2) | (1 << 3); asm volatile("msr PMCR_EL0, %0" : : "r"(pmcr)); // 重置计数器为0 asm volatile("msr PMCCNTR_EL0, %0" : : "r"(0ULL)); } uint64_t ReadTSC() { uint64_t tsc; // ISB确保之前的指令完成,保证读取的是最新值 asm volatile("isb; mrs %0, PMCCNTR_EL0" : "=r"(tsc)); return tsc; } int main() { // 必须先初始化PMU InitPMU(); uint64_t before = ReadTSC(); std::cout << "foo\n"; uint64_t after = ReadTSC(); std::cout << "Print took: " << after - before << std::endl; return 0; }
3. 编译与运行
编译时需要指定Apple M2架构,并且以root权限运行:
g++ -std=c++17 -mcpu=apple-m2 test.cpp -o test sudo ./test
替代方案(无需root权限)
如果无法获取root权限,可以使用Apple提供的mach_absolute_time(),它返回的抽象时间单位可转换为核心周期数,无需墙钟时间转换:
#include <cstdint> #include <iostream> #include <mach/mach_time.h> uint64_t ReadCoreCycles() { mach_timebase_info_data_t info; mach_timebase_info(&info); // 将抽象时间单位转换为核心周期数 return mach_absolute_time() * info.numer / info.denom; } int main() { uint64_t before = ReadCoreCycles(); std::cout << "foo\n"; uint64_t after = ReadCoreCycles(); std::cout << "Print took: " << after - before << std::endl; return 0; }
这个方法是Apple提供的用户态安全接口,无需特殊权限即可精准反映核心执行的周期数。
内容的提问来源于stack exchange,提问作者eof
相关产品推荐
相关产品推荐

