我正在创建一个实用程序,用于对不同的重复查找算法进行基准测试。为了准确地确定执行时间,我使用了从here获得的示例。它基本上是一个函数,它返回自CPU启动以来经过的CPU滴答量。我不是汇编专家所以我假设/希望代码示例是正确的。
这是我的主要功能。
int _tmain(int argc, _TCHAR* argv[])
{
// The size of the array that is going to be tested.
int Size = 1000000;
int * Array = GenerateRandomArray(Size);
// take a time measurement before.
__int64 TicksBefore = GetCpuClocks();
// Insert algorithm to benchmark here.
for (int i = 0; i < 100; i++)
{};
// take a time measurement afterwords.
__int64 TicksAfter = GetCpuClocks();
// calculate the amount of ticks that has passed.
__int64 TicksElapsed = TicksAfter - TicksBefore;
cout << "\nThe amount of ticks that has elapsed for this operation is: " << TicksElapsed << endl;
return 0;
}
我的问题,纯粹是出于好奇,为什么运行之间的经过时间不同?如果我在我的机器上按原样运行,我得到850到900之间的时间,总是10的倍数。如果我删除GenerateRandomArray(Size)
行,那么经过的时间增加到1010和1200!这不是我的火车粉碎。分布对我来说足够小,我仍然可以从中提取有价值的数据。
我真的很好奇为什么会这样。堆栈/堆的状态是否有效,或者它只是在我无法控制的系统中发生的中断?
答案 0 :(得分:4)
任何滴答级别的基准测试对现代处理器上的缓存状态都非常敏感(包括TLB,分支预测器等等)。为了得到一致的结果,你应该运行你的测试逻辑几次,取t0,运行逻辑数千甚至数百万次,然后取t1,减去和除。
一次点击主存储器在3 GHz CPU上可能花费约100个周期,并且缺少TLB虚拟地址转换等,可以在主存储器中击中3或4次!您的大内存分配可能会强制底层C / C ++运行时库通过系统调用从OS请求更多虚拟地址空间,这将导致两个模式切换并使用OS逻辑和数据填充一些缓存。 您需要隔离要测试的内容,“加热”缓存,并进行大量聚合测量以获得有意义的内容。
另外需要注意的是,现代超标量“乱序”CPU有一个非常宽松的概念,即“何时”操作就时钟而言。在不同的完整状态下,任何给定时间都可能有十几个操作挂起,有时由于错误预测的分支或推测的内存读取而导致状态将被回滚十几个时钟周期,而这些内存读取被另一个内核的写入无效。可以对指令进行广泛的重新排序,并且用于获取循环计数的默认RDTSC
指令易受此影响。较新的RDTSCP
指令是序列化的,这意味着它的顺序是强制执行的,但这实际上会花费CPU几个生产周期,以便能够清楚地说明该指令之前和之后的内容。