当前位置: 首页 > 面试题库 >

为什么该延迟环在无hibernate的几次迭代后开始运行得更快?

薛承志
2023-03-14
问题内容

考虑:

#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;
}

这是示例代码。在时序循环的前26次迭代中,该run函数的成本约为0.4 ms,但随后成本降低为0.2 ms。

usleep被注释掉,延迟环需要0.4毫秒所有的运行,从未加快。为什么?

代码是使用g++ -O0(无需优化)编译的,因此不会优化延迟循环。它可以在3.30 GHz的Intel®CoreTM
i3-3220

CPU 上运行,并具有3.13.0-32通用的Ubuntu
14.04.1
LTS(Trusty
Tahr)。


问题答案:

经过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 * 6 cycles/iteration周期为60万个周期,最多可控制几个高速缓存未命中(每个200个周期用于代码提取未命中,这会阻止进一步的指令发出,直到它们被解决为止)。

乱序执行和存储转发应在访问堆栈时(作为call指令的一部分)在大多数情况下隐藏潜在的高速缓存未命中。

即使将循环计数器保存在寄存器中,也要花费100k个周期。



 类似资料:
  • 我在玩node.js,我发现这个简单的程序运行得非常慢,我甚至没有等看它在3分钟后花了多长时间。 我使用不同的值进行了实验,发现1-112050需要3秒,而1-112051需要一分钟。这种突然的下降很奇怪。python中的相同程序或等效的shell脚本“seq 1 112051”在合理的时间内(0-2秒)运行。 请注意,此节点。js应用程序运行速度更快: 谁能给我解释一下为什么是node。js的行

  • 我一直在读一本面向初学者的书,“第一头HTML5编程”,其中有这样一段代码: 目前,如果我调用,它将返回下一次显示是在下午5:00。我将循环条件更改为“I<=movie.showtimes.length;”但它仍然只运行一次,并且只显示下午5点。循环只迭代一次,即使我重写了这个函数: 不是应该跑两次吗?

  • 我想在我的小部件构建完成后,在一定的延迟后执行一个函数。在Flatter中,这样做的惯用方法是什么? 我试图实现的是:我想从默认的FlutterLogo小部件开始,然后在一段时间后更改其样式属性。

  • 问题内容: 我需要在循环中对数据库进行SQL查询: 更好的方法是:保持原样或循环后移动: 或者是其他东西 ? 问题答案: 整个要点是直到函数返回才执行,因此将其放置在要关闭的资源打开后的适当位置。但是,由于要在循环内创建资源,因此根本不要使用defer- 否则,在函数退出之前,您不会关闭在循环内创建的任何资源,因此它们会堆积直到然后。相反,您应该在每次循环迭代结束时关闭它们, 而无需 :

  • 问题内容: 什么是Java的延迟加载?我不明白这个过程。有人可以帮助我了解延迟加载的过程吗? 问题答案: 假设您有一个父母,而那个父母有很多孩子。Hibernate现在可以“延迟加载”子级,这意味着它在加载父级时实际上并不会加载所有子级。而是在要求时加载它们。您可以显式地请求此请求,或者,更常见的是,当您尝试访问孩子时,hibernate会自动加载它们。 延迟加载可以帮助显着提高性能,因为通常您不

  • 我写了一些欧拉问题35的代码: 我想知道为什么这个代码(上图)运行得这么快,当我设置在函数中。这段代码的运行时间约为8秒。最初,我没有设置,我的函数是这样的: 我的初始代码(没有)运行了很长时间,我没有等它完成。我很好奇,为什么这两段代码在运行时间上存在如此大的差异,因为我不相信会有任何重复项,这会使迭代花费如此长的时间,以至于迭代。也许我对set()的想法是错误的。欢迎任何帮助。