您能否告诉我为什么以下程序打印的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 级缓存中。如果
另一件可能使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"监视操作系统调度程序事件。