两个语句执行之间的时间差不一致

Time difference between execution of two statements is not consistent

本文关键字:之间 时间差 不一致 执行 语句 两个      更新时间:2023-10-16

您能否告诉我为什么以下程序打印的timediff值通常是 4 微秒(不同运行的范围为 90 到 1000 次),但在某些情况下有时为 70 或更多微秒(不同运行的范围为 2 到 10 倍):

#include <iostream>
using namespace std;
#include<sys/time.h>
#define MAXQ 1000000
#define THRDS 3
double GetMicroSecond()
{
    timeval tv;
    gettimeofday (&tv, NULL);
    return (double) (((double)tv.tv_sec * 1000000) + (double)tv.tv_usec);
}
int main()
{
        double timew, timer, timediff;
        bool flagarray[MAXQ];
        int x=0, y=0;
        for(int i=0; i<MAXQ; ++i)
            flagarray[i] = false;
        while(y <MAXQ)
       {
            x++;
            if(x%1000 == 0)
            {
                    timew = GetMicroSecond();
                    flagarray[y++]=true;
                    timer = GetMicroSecond();
                    timediff = timer - timew;
                    if(timediff > THRDS) cout << timer-timew << endl;
            }
       }
}

编译使用:g++ testlatency.cpp -o testlatency

注意:在我的系统中有 12 个内核。仅通过系统中运行的此程序来检查性能。

timew = GetMicroSecond();
flagarray[y++]=true;
计时器 = 获取微秒();

语句 flagarray[y++]=true; 在现代计算机上执行所需的时间要少得多,如果 flagarray[y++ ] 恰好位于 1 级缓存中。如果

该位置位于二级缓存中但不在一级缓存中,则执行该语句所需的时间更长;如果该位置位于 3 级缓存中但不在 1 级或 2 级缓存中,则执行该语句的时间要长得多;如果该位置不在任何缓存中,则执行该语句的时间要长得多。

另一件可能使timer-timew超过三毫秒的事情是程序向操作系统屈服时。 缓存未命中可能会导致产量。系统调用也是如此。函数gettimeofday是系统调用。作为一般规则,您应该期望任何系统调用都会产生。


注意:在我的系统中有 12 个内核。仅通过系统中运行的此程序来检查性能。

这不是真的。总是有许多其他程序,以及许多其他线程在 12 核计算机上运行。其中包括操作系统本身(它本身包含许多线程),以及很多很多的小守护进程。每当程序生成时,操作系统都可以决定暂时挂起程序,以便暂时挂起但要求使用 CPU 的无数其他线程之一。

其中一个守护程序是网络时间协议守护程序 (ntpd)。这会对您的系统时钟执行各种时髦的小操作,以使其与原子钟保持同步。通过一个微小的指令,例如flagarray[y++]=true是连续调用gettimeofday之间唯一的东西,你甚至可能会看到时间偶尔倒流。


在测试计时时,最好在粗略的级别进行计时。不要对不涉及任何函数调用的单个语句进行计时。对循环进行计时比对循环体的单独执行进行计时要好得多。即便如此,由于缓存未命中以及操作系统暂时暂停程序的执行,您也应该预料到计时会有一些变化。

现代基于Unix的系统比不受网络时间协议守护程序更改的gettimeofday具有更好的计时器(例如,clock_gettime)。您应该使用其中之一而不是gettimeofday

通常,有许多线程共享少量内核。除非采取措施确保线程不间断地使用内核,否则无法保证操作系统不会决定在两个调用GetMicroSecond()调用之间抢占线程,并让其他线程暂时使用该内核。

即使你的代码不间断地运行,你尝试计时的行:

flagarray[y++]=true;

执行所需的时间可能比测量代码本身少得多。

在程序执行的同时,现代操作系统内部会发生许多事情。他们中的一些人可能会从您的程序中"窃取"CPU,如NPE的答案中所述。更多可能影响时间的示例:

  • 来自设备的干扰(计时器、硬盘、网络接口等);
  • 访问内存(缓存)

这些都不容易预测。

如果您在某个微控制器上运行您的代码,或者可能使用实时操作系统,则可以期待一致性。

有很多变量可以解释看到的不同时间值。我会更关注

  • 缓存未命中/填充
  • 调度程序事件
  • 中断

    布尔旗阵[MAXQ];

既然您将 MAXQ 定义为 1000000,我们假设flagarray占用 1MB 的空间。

您可以根据 L1/L2 D 缓存大小计算可能发生的缓存未命中次数。然后,您可以关联填充所有 L1 并开始缺失所需的迭代次数,与 L2 相同。操作系统可能会取消您的进程并重新安排它 - 但是,由于您拥有的内核数量,我希望不太可能。中断也是如此。空闲系统永远不会完全空闲。您可以选择将过程仿射到一个核心数字,例如 N

taskset 0x<MASK> ./exe并控制其执行。

如果你真的很好奇,我建议你使用大多数Linux发行版上提供的"perf"工具。

你可以这样做

perf stat -e L1-dcache-loadmisses

perf stat -e LLC-load-misses

一旦你有了这些数字和迭代次数,你就可以开始构建导致注意到的滞后的活动的图片。您还可以使用"perf stat"监视操作系统调度程序事件。