经过26次迭代后,Linux将CPU提升至最大时钟速度,因为您的进程连续两次使用其全部时间片。
如果您使用性能计数器而不是挂钟时间进行检查,您会发现每个延迟环的核心时钟周期保持恒定,从而确认这只是DVFS的作用(所有现代CPU都使用DVFS以更高的能耗运行-
大部分时间都是有效的频率和电压)。
如果您在支持新电源管理模式(硬件完全控制时钟速度的内核)的Skylake上进行了测试,则加速会更快。
如果您将它在带有Turbo的Intel
CPU上运行一段时间,则可能会发现,一旦散热限制要求时钟速度降低到最大持续频率,每次迭代的时间就会再次稍微增加。(有关Turbo的更多信息,请参见为什么我的CPU无法在HPC中保持峰值性能,更多有关Turbo使CPU的运行速度超过其在高功率工作负载下的承受能力的信息。)
引入ausleep
可以防止Linux的CPU频率调节器提高时钟速度,因为即使在最低频率下,该过程也不会产生100%的负载。(即,内核的启发式方法决定CPU的运行速度足以满足其上正在运行的工作负载。)
缓存/ TLB污染对于该实验根本不重要
。除了堆栈的末尾,时序窗口内基本上没有其他东西可以接触内存。大部分时间都花在一个很小的循环(1行指令高速缓存)中,该循环仅接触
int堆栈存储器之一。
usleep对于此代码,任何潜在的高速缓存污染时间仅占该代码时间的一小部分(实际代码将有所不同)!
对于x86更详细:
对其
clock()自身的调用可能会丢失高速缓存,但是代码获取高速缓存未命中会延迟开始时间的测量,而不是被测量的一部分。
clock()几乎永远不会延迟对的第二次调用,因为它在缓存中仍然很热。
该
run函数可能位于与之不同的缓存行中
main(因为gcc标记
main为“冷”,因此它的优化程度较低,并与其他冷函数/数据一起放置)。我们可以预期会有一两个指令高速缓存未命中。但是,它们可能仍在同一4k页面中,因此
main在进入程序的定时区域之前将触发潜在的TLB丢失。
gcc
-O0会将OP的代码编译为如下代码(Godbolt编译器浏览器):将循环计数器保存在堆栈中的内存中。
空循环将循环计数器保持在堆栈内存中,因此,在典型的Intel x86
CPU上,循环的运行是在OP的IvyBridge
CPU上每6个周期执行一次迭代,这要归功于
add存储目标的一部分的存储转发延迟(读-modify-write)。
100k iterations *6cycles/iteration周期为60万个周期,最多可控制几个高速缓存未命中(每个200个周期用于代码提取未命中,这会阻止进一步的指令发出,直到它们被解决为止)。
乱序执行和存储转发应在访问堆栈时(作为
call指令的一部分)在大多数情况下隐藏潜在的高速缓存未命中。
即使将循环计数器保存在寄存器中,也要花费100k个周期。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)