为什么这个延迟循环在没有睡眠的情况下经过几次迭代后开始运行得更快?
- 2024-10-17 08:46:00
- admin 原创
- 73
问题描述:
考虑:
#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 毫秒,但随后耗时减少至 0.2 毫秒。
当usleep
取消注释时,延迟循环在所有运行中花费 0.4 毫秒,从未加速。为什么?
代码编译时g++ -O0
未进行优化,因此延迟循环未经过优化。它在 Intel(R) Core(TM) i3-3220 CPU @ 3.30 GHz 上运行,使用 3.13.0-32-generic Ubuntu 14.04.1 LTS (Trusty Tahr)。
解决方案 1:
经过 26 次迭代后,由于您的进程连续几次使用了其完整时间片,因此 Linux 会将 CPU 提升到最大时钟速度。
如果您使用性能计数器而不是挂钟时间进行检查,您会发现每个延迟循环的核心时钟周期保持不变,证实这只是DVFS的效果(所有现代 CPU 在大多数情况下都使用它来以更节能的频率和电压运行)。
如果您在具有内核支持新电源管理模式(硬件完全控制时钟速度)的Skylake上进行测试,则加速将会更快。
如果您让它在具有 Turbo 的 Intel CPU上运行一段时间,一旦热限制要求时钟速度降低回最大持续频率,您可能会看到每次迭代的时间再次略有增加。 (有关 Turbo 让 CPU 运行速度超过其在高功率工作负载下可以维持的速度的更多信息,请参阅为什么我的 CPU 无法在 HPC 中保持峰值性能。)
引入会usleep
阻止Linux 的 CPU 频率调节器提高时钟速度,因为即使在最低频率下,该进程也不会产生 100% 的负载。(即内核的启发式方法决定 CPU 的运行速度足以应付其上运行的工作负载。)
对其他理论的评论:
回复:David 的理论,即潜在的上下文切换usleep
可能会污染缓存:这通常不是一个坏主意,但它无助于解释这段代码。
对于这个实验来说,缓存/TLB 污染根本不重要。除了堆栈末尾之外,计时窗口内基本上没有任何东西会接触内存。大部分时间都花在一个很小的循环(1 行指令缓存)中,该循环仅接触int
堆栈内存之一。期间任何潜在的缓存污染都usleep
只是此代码时间的一小部分(实际代码会有所不同)!
对于 x86 的更多详细信息:
对其自身的调用clock()
可能会发生缓存未命中,但代码提取缓存未命中会延迟开始时间测量,而不是成为测量的一部分。对的第二次调用clock()
几乎永远不会被延迟,因为它在缓存中应该仍然很热。
该run
函数可能位于不同的缓存行中main
(因为 gcc 标记main
为“冷”,因此它优化较少,并与其他冷函数/数据放在一起)。我们可以预期一到两个指令缓存未命中。不过,它们可能仍在同一个 4k 页中,因此main
在进入程序的定时区域之前会触发潜在的 TLB 未命中。
gcc -O0 将会把 OP 的代码编译成类似这样的内容(Godbolt Compiler explorer):将循环计数器保存在堆栈的内存中。
空循环将循环计数器保存在堆栈内存中,因此在典型的Intel x86 CPU上,循环在 OP 的 IvyBridge CPU 上每 ~6 个周期运行一次迭代,这要归功于存储转发延迟,它是add
内存目标(读取 - 修改 - 写入)的一部分。 100k iterations * 6 cycles/iteration
是 600k 个周期,这主导了最多几个缓存未命中的贡献(每个代码提取未命中约 200 个周期,这会阻止进一步的指令发出,直到它们被解决)。
无序执行和存储转发应该主要隐藏访问堆栈时的潜在缓存未命中(作为call
指令的一部分)。
即使循环计数器保存在寄存器中,100k 个周期也是很多的。
解决方案 2:
调用usleep
可能会也可能不会导致上下文切换。如果会导致,则所需时间会比不会导致时更长。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件