在最近的CPU上(至少在过去十年左右),除了各种可配置的性能计数器之外,英特尔还提供了三个固定功能硬件性能计数器。三个固定计数器是:
INST_RETIRED.ANY
CPU_CLK_UNHALTED.THREAD
CPU_CLK_UNHALTED.REF_TSC
第一个计算退役指令,第二个计算实际周期,最后一个是我们感兴趣的。英特尔软件开发人员手册第3卷的描述是:
此事件计算TSC速率时的参考周期数 核心不处于暂停状态而不处于TM停止时钟状态。该 当核心运行HLT指令时,核心进入暂停状态 MWAIT指令。此事件不受核心频率的影响 改变(例如,P状态)但是以与时间相同的频率计数 邮票柜台。此事件可以近似核心的经过时间 没有处于暂停状态而且没有处于TM stopclock状态。
因此,对于CPU绑定循环,我希望此值与从rdstc
读取的自由运行TSC值相同,因为它们应该仅针对暂停的循环指令或“TM stopclock states”进行分歧“是。
我使用以下循环(整个standalone demo is available on github)测试:
for (int i = 0; i < 100; i++) {
PFC_CNT cnt[7] = {};
int64_t start = nanos();
PFCSTART(cnt);
int64_t tsc =__rdtsc();
busy_loop(CALIBRATION_LOOPS);
PFCEND(cnt);
int64_t tsc_delta = __rdtsc() - tsc;
int64_t nanos_delta = nanos() - start;
printf(CPU_W "d" REF_W ".2f" TSC_W ".2f" MHZ_W ".2f" RAT_W ".6f\n",
sched_getcpu(),
1000.0 * cnt[PFC_FIXEDCNT_CPU_CLK_REF_TSC] / nanos_delta,
1000.0 * tsc_delta / nanos_delta,
1000.0 * CALIBRATION_LOOPS / nanos_delta,
1.0 * cnt[PFC_FIXEDCNT_CPU_CLK_REF_TSC]/tsc_delta);
}
定时区域唯一重要的是busy_loop(CALIBRATION_LOOPS);
,它只是一个紧密的易失性商店循环,gcc
由clang
和void busy_loop(uint64_t iters) {
volatile int sink;
do {
sink = 0;
} while (--iters > 0);
(void)sink;
}
按一个周期执行迭代最近的硬件:
PFCSTART
PFCEND
和CPU_CLK_UNHALTED.REF_TSC
命令使用as compiled读取__rdtsc()
计数器。 rdtsc
是通过nanos()
指令读取TSC的内在函数。最后,我们使用int64_t nanos() {
auto t = std::chrono::high_resolution_clock::now();
return std::chrono::time_point_cast<std::chrono::nanoseconds>(t).time_since_epoch().count();
}
来衡量实时,这只是:
cpuid
是的,我没有发出CPU# REF_TSC rdtsc Eff Mhz Ratio
0 2392.05 2591.76 2981.30 0.922946
0 2381.74 2591.79 3032.86 0.918955
0 2399.12 2591.79 3032.50 0.925660
0 2385.04 2591.79 3010.58 0.920230
0 2378.39 2591.79 3010.21 0.917663
0 2355.84 2591.77 2928.96 0.908970
0 2364.99 2591.79 2942.32 0.912492
0 2339.64 2591.77 2935.36 0.902720
0 2366.43 2591.79 3022.08 0.913049
0 2401.93 2591.79 3023.52 0.926747
0 2452.87 2591.78 3070.91 0.946400
0 2350.06 2591.79 2961.93 0.906733
0 2340.44 2591.79 2897.58 0.903020
0 2403.22 2591.79 2944.77 0.927246
0 2394.10 2591.79 3059.58 0.923723
0 2359.69 2591.78 2957.79 0.910449
0 2353.33 2591.79 2916.39 0.907992
0 2339.58 2591.79 2951.62 0.902690
0 2395.82 2591.79 3017.59 0.924389
0 2353.47 2591.79 2937.82 0.908047
,并且事情没有以精确的方式交错,但校准循环是一整秒,所以这样的纳秒级问题只是被稀释到或多或少什么都没有。
启用TurboBoost后,这是我的i7-6700HQ Skylake CPU上典型运行的前几个结果:
REF_TSC
此处,rdtsc
是如上所述的固定TSC性能计数器,rdtsc
是Eff Mhz
指令的结果。 Ratio
是在该时间间隔内有效计算的真实CPU频率,主要是为了好奇而显示,以及快速确认涡轮增压的数量。REF_TSC
是rdtsc
和rdstc
的比率CPU# REF_TSC rdtsc Eff Mhz Ratio
0 2592.26 2592.25 2588.30 1.000000
0 2592.26 2592.26 2591.11 1.000000
0 2592.26 2592.26 2590.40 1.000000
0 2592.25 2592.25 2590.43 1.000000
0 2592.26 2592.26 2590.75 1.000000
0 2592.26 2592.26 2590.05 1.000000
0 2592.25 2592.25 2590.04 1.000000
0 2592.24 2592.24 2590.86 1.000000
0 2592.25 2592.25 2590.35 1.000000
0 2592.25 2592.25 2591.32 1.000000
0 2592.25 2592.25 2590.63 1.000000
0 2592.25 2592.25 2590.87 1.000000
0 2592.25 2592.25 2590.77 1.000000
0 2592.25 2592.25 2590.64 1.000000
0 2592.24 2592.24 2590.30 1.000000
0 2592.23 2592.23 2589.64 1.000000
0 2592.23 2592.23 2590.83 1.000000
0 2592.23 2592.23 2590.49 1.000000
0 2592.23 2592.23 2590.78 1.000000
0 2592.23 2592.23 2590.84 1.000000
0 2592.22 2592.22 2588.80 1.000000
列。我预计这将非常接近1,但在实践中我们看到它在0.90到0.92之间徘徊并伴随着很多变化(我在其他运行中看到它低至0.8)。
图形上看起来像 2 :
hlt
调用几乎返回精确结果 1 ,而PMU TSC计数器遍布整个地方,有时几乎低至2300 MHz。
如果我关闭turbo ,结果会更加一致:
mwait
基本上,该比率为1.000000至 6位小数。
以图形方式(Y轴刻度强制与上一图相同):
现在代码只是运行一个热循环,并且应该没有2591.97 MHz
或rdstc
指令,当然没有任何暗示变化超过10%的指令。我不能说肯定什么是“TM停止时钟周期”,但我敢打赌它们是“热管理停止时钟周期”,这是一种用来临时限制CPU的技巧它的最高温度。然而,我查看了集成的热敏电阻读数,我从未看到CPU突破60C,远远低于90C-100C,正常管理开始(我认为)。
知道这可能是什么?在不同的turbo频率之间是否存在隐含的“停止周期”?这肯定发生,因为盒子不安静,因此涡轮增压频率随着其他核心开始和停止工作在背景材料上跳跃(最大涡轮机频率直接取决于活动核心数量:在我的盒子上它是3.5, 3.3,3.2,3.1 GHz分别用于1,2,3或4个核心活动。
1 事实上,有一段时间我真的得到精确结果到两位小数:ntpd
- 迭代后的迭代。然后一些事情发生了变化,我不确定是什么,rdtsc
结果中的变化很小,约为0.1%。一种可能性是逐步调整时钟,由Linux时序子系统进行调整,以使本地晶体导出的时间与{{1}}确定的时间内联。也许,它只是一个晶体漂移 - 上面的最后一个图表显示每秒{{1}}的测量周期稳定增加。
2 图表与文本中显示的值的运行不对应,因为每次更改文本输出格式时我都不会更新图表。然而,定性行为在每次运行时基本相同。