是什么原因导致我的循环在第一次迭代中运行得更慢?

问题描述 投票:5回答:5

我编写了以下程序来使用std :: chrono:

#include <iostream>
#include <chrono>

int main(int argc, char** argv) {
    const long iterationCount = 10000;
    long count = 0;
    for(long i = 0; i < iterationCount; i++) {
        auto start = std::chrono::high_resolution_clock::now();
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        count++;count++;count++;count++;count++;count++;count++;count++;count++;count++;
        auto end = std::chrono::high_resolution_clock::now();

        auto timeTaken = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        std::cout << timeTaken << " " << std::endl;
    }
}

我使用G ++编译了这个,没有启用编译器优化:

g++ chrono.cpp -o chrono

我随后运行了几次这个程序,得到了一个有趣的模式。对于前500-1000次迭代,程序运行速度比其余迭代速度慢约7-8倍。

以下是该程序的示例输出:https://pastebin.com/tUQsQEAQ

导致运行时间出现这种差异的原因是什么?我的第一反应是缓存,但是那个不会很快变得饱和吗?

如果重要,我的操作系统是Ubuntu 18.04,我的g ++版本是7.3.0。

c++ performance optimization
5个回答
3
投票

在微架构实现定义时间之后,如果CPU可以找到相同的热功率余量,则频率调整会启动以将要求核心的时钟加速到最大值(在TDP的限制范围内)。

英特尔的实施称为Turbo boost

如果在系统中禁用频率缩放(例如,使用sudo cpupower frequency-set --governor performance - cpupowercpupowerutils包中),每次迭代的时间或多或少相同。


循环本身非常容易预测,如果不仅仅是循环控制结束时的一个错误预测,你可以预期只有一些错误预测 - C ++库中的代码可能更难预测,但即使这样也不会花那么长时间( 1000次迭代)让BPU赶上您的代码。 所以你可以排除分支错误预测。

或多或少适用于I-cache(除了C ++库实现对变量使用很重要之外,几乎没有使用D-cache)。 代码应足够小,以适应L1-I,甚至在DSB中也是如此。 L1-I未命中不需要1000次迭代来解决,并且如果缓存中存在严重的设置冲突,这将显示为在1000次迭代后不会消失的一般减速。

一般来说,由于CPU第一次填充缓存(数据,指令,TLB),因此代码在循环携带的依赖链的第二次迭代中运行得更快是一种已知的效果。 如果CPU资源耗尽,它最终会再次减速,例如,如果存在大量端口压力(例如许多相同的有限端口长延迟指令),RS可能会填满停止FE或者是否存在很多加载/存储填充MOB / SB / LB或大量跳转填充BOB。 然而,这些影响非常迅速地发挥作用,以至于它们支配着代码的运行时间。 在这种情况下,减速发生得非常晚(在CPU时间内),这使得需要考虑按需过程 - 如Turbo boost。


1
投票

正如其他人所说,几乎可以肯定您会体验到动态CPU频率缩放的效果。

我可以在我的机器上重现您的结果。但是,如果我使用cpufreq-set实用程序关闭动态CPU频率缩放(以使CPU以恒定频率运行),您看到的效果就会消失。


0
投票

我猜想时钟分辨率可能就是原因。您处于可能低于系统分辨率的范围内。请考虑linux documentation的这句话(由我粗体):

从Linux 2.6.21开始,Linux支持高分辨率计时器(HRT),可选择通过CONFIG_HIGH_RES_TIMERS进行配置。在支持HRT的系统上,睡眠和定时器系统调用的准确性不再受jiffy的限制,而是可以像硬件允许的那样精确(微秒精度是现代硬件的典型值)。


0
投票

我没有linux物理机来测试这个,但是在Windows 10 x64(i7)盒子上运行我得到的结果如...

395
16592
395
395
395
790
395
790
395
395
395
790

哪个匹配您的跟踪结束。在Windows上,值395似乎与时钟计数器锁相,因此时间长度为395,790或非常大的数字(例如116592)。真正大的数字看起来像上下文切换 - 我们的程序没有运行。

我的Windows机器上的程序没有最初的减速。

但是结果一般,看起来非常类似于pastebin文件中的结果。

所以问题为什么循环在开始时需要更长的时间。我们可以看到它不是上下文切换,因为它们看起来明显更长(18k)。因此,必须是处理器在程序开始时执行其工作的速度较慢。这种缓慢的原因可能是CPU上的其他核心正在清除核心的缓存,或者使用CPU的共享缓存。

我会通过为系统添加一个初始睡眠来验证这一点,以便在启动时稳定下来,仅在机器安定后运行循环。


0
投票

在大多数机器上,当您开始测试时,CPU通常处于较低功率状态,运行频率较低 - 但随着测试继续运行,CPU将速度提高到最大频率。

因此,常见的模式是看前几毫秒的慢速时间,之后运行时间减少(随着CPU加速)并最终稳定下来。

© www.soinside.com 2019 - 2024. All rights reserved.