执行相同代码之间的CPU时间是否应该始终相同



我对CPU时间的理解是,在同一台机器上,每次执行之间的时间应该总是相同的。它每次都需要相同数量的cpu周期。

但我现在正在运行一些测试,执行一个基本的回声"Hello World",它给了我0.003到0.005秒的时间。

我对CPU时间的理解是错误的,还是我的测量有问题?

您的理解完全错误。在现代CPU上运行现代操作系统的真实世界计算机并不是简单的理论抽象。有各种各样的因素可以影响代码执行所需的CPU时间。

考虑内存带宽。在一台典型的现代机器上,运行在机器核心上的所有任务都在竞争对系统内存的访问。如果代码在运行的同时,另一个内核上的代码使用了大量的内存带宽,这可能会导致对RAM的访问占用更多的时钟周期。

许多其他资源也是共享的,例如缓存。假设代码经常被中断,让其他代码在内核上运行。这意味着代码将经常发现缓存处于冷状态,并导致大量缓存未命中。这也将导致代码占用更多的时钟周期。

让我们也谈谈页面错误。代码本身可能在内存中,也可能在代码开始运行时不在内存中。即使代码在内存中,您也可能会发生软页面故障(以更新操作系统对活动使用的内存的跟踪),这取决于该页面上次发生软页面错误的时间或加载到RAM的时间。

你的基本helloworld程序是对终端进行I/O操作。所花费的时间可能取决于当时与终端交互的其他内容。

对现代系统的最大影响包括:

  • 如果虚拟内存在pagecache中不是热的,它会从磁盘延迟分页代码和数据。(程序的首次运行往往会有更多的启动开销。)
  • CPU频率不固定(怠速/涡轮转速.grep MHz /proc/cpuinfo)
  • CPU缓存可能很热,也可能不热
  • (在很短的时间间隔内)在您的定时区域内随机发生或不发生的中断

所以即使循环是固定的(它们不是固定的),你也不会看到相等的时间。

您的假设并非完全错误,但它仅适用于单个循环的核心时钟周期,并且仅适用于不涉及任何内存访问的情况(例如,数据在L1d缓存中已经很热,代码在CPU内核内的L1i缓存中已经热)。并且假设在定时循环运行时没有发生中断。

运行一个完整的程序是一个更大规模的操作,并且将涉及共享资源(以及对它们的可能争用),比如访问主存。正如@David所指出的,在终端模拟器上打印字符串write系统调用-如果你的程序最终在等待,那么与另一个进程的通信可能会很慢,并且需要唤醒另一个程序。重定向到/dev/null或常规文件会删除它,或者像./hello >&-一样关闭stdout会使你的write系统调用返回-EBADF(在Linux上)。

现代CPU是非常复杂的野兽。您可能有一个执行无序的Intel或AMD x86-64 CPU,以及十几个左右用于传入/传出缓存线的缓冲区,使其能够跟踪大约那么多未处理的缓存未命中(内存级并行性)。每个核心有两个级别的专用缓存,以及一个共享的三级缓存。祝你好运,除了最可控的条件外,还能预测出确切的时钟周期数。

但是,是的,如果确实控制了条件,那么相同的小循环通常会在每次迭代的核心时钟周期数相同的情况下运行。

然而,即使也不总是如此。我看到过这样的情况,同一个循环似乎有两个稳定的状态,CPU如何调度指令。在数百万次循环迭代中,不同的进入条件怪癖可能会导致持续的速度差异。

我偶尔会在Sandybridge和Skylake等现代英特尔CPU上进行微基准测试时看到这种情况。即使在性能计数器和https://agner.org/optimize

在我记忆中的一个例子中,中断往往会使循环进入高效的执行模式@BeeOnRope在短时间内使用或RDPMC(或者可能是核心时钟固定的RDTSC=TSC参考时钟)测量缓慢的循环/迭代,而我通过使用非常大的重复计数和对整个程序使用perf-stat来测量它的运行速度(这是一个静态可执行文件,只有一个循环是用asm手工编写的)。@Bee能够通过增加迭代次数来重新处理我的结果,这样中断就会发生在定时区域内,从中断中返回往往会让CPU摆脱非最佳的uop调度模式,无论是什么。

最新更新