考虑:

#include <time.h>
#include <unistd.h>
#include <iostream>
using namespace std;

const int times = 1000;
const int N = 100000;

void run() {
  for (int j = 0; j < N; j++) {
  }
}

int main() {
  clock_t main_start = clock();
  for (int i = 0; i < times; i++) {
    clock_t start = clock();
    run();
    cout << "cost: " << (clock() - start) / 1000.0 << " ms." << endl;
    //usleep(1000);
  }
  cout << "total cost: " << (clock() - main_start) / 1000.0 << " ms." << endl;
}

Here is the example code. In the first 26 iterations of the timing loop, the run function costs about 0.4 ms, but then the cost reduces to 0.2 ms.

When the usleep is uncommented, the delay-loop takes 0.4 ms for all runs, never speeding up. Why?

The code is compiled with g++ -O0 (no optimization), so the delay loop isn't optimized away. It's run on Intel(R) Core(TM) i3-3220 CPU @ 3.30 GHz, with 3.13.0-32-generic Ubuntu 14.04.1 LTS (Trusty Tahr).

推荐答案

经过26次迭代后,Linux将CPU提升到最大时钟速度,因为您的进程连续几次使用其全部time slice次.

如果你用性能计数器而不是墙上的时钟时间来判断,你会发现每个延迟环路的核心时钟周期保持不变,这证实了它只是DVFS的效应(所有现代CPU在大多数时间都以更节能的频率和电压运行).

如果在内核支持new power-management mode (where the hardware takes full control of the clock speed)Skylake上进行测试,升级速度会快得多.

If you leave it running for a while on an Intel CPU with Turbo, you'll probably see the time per iteration increase again slightly once thermal limits require the clock speed to reduce back down to the maximum sustained frequency. (See Why can't my CPU maintain peak performance in HPC for more about Turbo letting the CPU run faster than it can sustain for high-power workloads.)


Introducing a 101防止Linux's CPU frequency governor提高时钟速度,因为即使在最低频率下,该过程也不会产生Linux's CPU frequency governor%的负载.(也就是说,内核的启发式算法决定CPU的运行速度足以满足其上运行的工作负载.)



comments on other theories:

回复:David's theory that a potential context switch from usleep could pollute caches:一般来说,这不是一个坏主意,但这无助于解释这段代码.

Cache / TLB pollution isn't important at all for this experiment.在计时窗口内,除了堆栈的末尾,基本上没有任何东西能触及内存.大部分时间都花在一个小循环(1行指令缓存)中,该循环只涉及int个堆栈内存.usleep期间任何潜在的缓存污染都只占该代码时间的一小部分(实际代码将有所不同)!

有关x86的更多详细信息:

clock()本身的调用可能会缓存未命中,但代码获取缓存未命中会延迟开始时间测量,而不是作为测量的一部分.对clock()的第二次调用几乎永远不会被延迟,因为它在缓存中应该仍然是热的.

run函数可能位于与main不同的缓存线中(因为gcc将main标记为"冷",所以它得到的优化更少,并与其他冷函数/数据放在一起).我们预计会有一两个instruction-cache misses.不过,它们可能仍在同一个4k页面中,因此在进入程序的定时区域之前,main将触发潜在的TLB未命中.

gcc -O0 will compile the OP's code to something like this (Godbolt Compiler explorer): keeping the loop counter in memory on the stack.

空循环将循环计数器保留在堆栈内存中,因此在典型的Intel x86 CPU次循环中,循环在OP的IvyBridge CPU上每6个周期运行一次,这要归功于存储转发延迟,它是add的一部分,带有内存目标(读修改写).100k iterations * 6 cycles/iteration是600k个周期,这是最多两个缓存未命中的主要原因(每个代码提取未命中约200个周期,在解决之前阻止发出更多指令).

无序执行和存储转发应该主要隐藏访问堆栈时可能出现的缓存未命中(作为call指令的一部分).

即使循环计数器保存在一个寄存器中,100k周期也是很多.

Linux相关问答推荐

我使用Windows 10,但我无法在我的WSL2上下载vscode

在Zenity进度窗口上单击取消后如何停止bash脚本

我想显示包含一个方括号的行,方括号可以是开括号,也可以是闭括号.

UTF-8输入和使用XGetICValues

匹配模式和提取

如何让xargs对 bash 脚本中find命令找到的所有文件执行?

如何在具有多种可能性的linux shell中获取最大值和最小值?

为什么当凭证助手设置为存储 SSH 远程存储库时 git pull 不使用 .git-credentials

如何比较 2 个文件并将第二个文件的所有行打印到输出文件 awk

在 linux 中插入带有 sed 命令的文件的行

读取命令停止执行 bash 脚本

根据其他列的值创建一个新列

在 SLURM 作业(job)脚本中设置和传递字符串变量

使远程目录保持最新

ldconfig 错误:使用 Linux 加载程序时不是符号链接

如何通过进程名获取PID?

`os.symlink` 与 `ln -s`

在linux中将制表符分隔的文件转换为csv的最快方法

Linux 目录列表中只有问号

在具有完整路径的 Linux 中使用 ls 命令列出文件