为什么while循环的执行时间看起来如此奇怪
Why is the execution time of a while-loop appears so weird?
我使用rdstc()函数分别测试while循环的内外执行时间,两个结果有很大的差异。当我从外部进行测试时,结果大约是445亿次循环。当我从里面测试时,结果大约是330亿次循环。
代码段如下所示:
while(true){
beginTime = rdtsc();
typename TypedGlobalTable<K, V, V, D>::Iterator *it2 = a->get_typed_iterator(current_shard(), false);
getIteratorTime += rdtsc()-beginTime;
if(it2 == NULL) break;
uint64_t tmp = rdtsc();
while(true) {
beginTime = rdtsc();
if(it2->done()) break;
bool cont = it2->Next(); //if we have more in the state table, we continue
if(!cont) break;
totalF2+=it2->value2(); //for experiment, recording the sum of v
updates++; //for experiment, recording the number of updates
otherTime += rdtsc()-beginTime;
//cout << "processing " << it2->key() << " " << it2->value1() << " " << it2->value2() << endl;
beginTime = rdtsc();
run_iter(it2->key(), it2->value1(), it2->value2(), it2->value3());
iterateTime += rdtsc()-beginTime;
}
flagtime += rdtsc()-tmp;
delete it2; //delete the table iterator}
我测试的while循环是内部循环。
rdstc()函数如下所示:
static uint64_t rdtsc() {
uint32_t hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return (((uint64_t)hi)<<32) | ((uint64_t)lo);
}
我在一个虚拟机中在Ubuntu 10.04LTS下构建并运行了这个程序,内核版本是"Linux Ubuntu 2.6.32-38-generic#83 Ubuntu SMP Wed Jan 4 11:13:04 UTC 2012 i686 GNU/Linux"。
RDTSC
指令不是"serializing"
,请参阅此SO问题
为什么不是';RDTSC不是串行指令吗?
一些背景
现代X86内核具有"无序"(OoO)执行,这意味着一旦操作数准备就绪且执行单元可用,指令就会被调度到能够执行指令的execution unit
。。。指令不一定按程序顺序执行。指令do按程序顺序退出,因此您可以获得寄存器和内存的精确内容,当发生中断、异常或故障时,按顺序执行体系结构会指定这些内容。
这意味着CPU可以自由地以任何顺序调度指令执行,以获得尽可能多的并发性并提高性能,只要它给人一种指令按顺序执行的错觉。
RDTSC
指令被设计为尽可能快地执行,尽可能不具有侵入性,开销很小。它有大约22个处理器周期延迟,但您可以同时完成大量工作。
有一个新的变体,称为RDTSCP
,正在序列化。。。处理器按照程序顺序等待以前的指令完成,并阻止将来的指令被调度。。。从性能的角度来看,这是昂贵的。
回到你的问题
考虑到这一点,想想编译器生成了什么,处理器看到了什么。。。while(true)只是一个无条件分支,它并不是真正的执行,而是被流水线的前端,即指令解码器所消耗,它正在尽可能地提前获取,将指令塞进指令调度器,以尝试在每个周期内获得尽可能多的执行的指令。因此,循环中的RDTSC
指令被调度,其他指令继续流动和执行,最终RDTSC
失效,结果被转发到依赖于结果的指令(代码中的减法)。但您并没有真正精确地计时内部循环。
让我们看看下面的代码:
beginTime = rdtsc();
run_iter(it2->key(), it2->value1(), it2->value2(), it2->value3());
iterateTime += rdtsc()-beginTime;
假设函数run_iter()
在返回后调用rdtsc()
时已经完成。但真正可能发生的是,run_iter
中内存的一些加载在缓存中未命中,处理器保持该加载在内存上等待,但它可以继续执行独立的指令,它从函数返回(或函数被编译器内联),并在返回时看到RDTSC
,因此它调度。。。嘿,它不依赖于缓存中丢失的负载,也不序列化,所以这是公平的游戏。RDTSC
在22个周期内退役,这比进入DRAM的缓存未命中(数百个周期)快得多。。。突然之间,报告不足执行run_iter()
所花费的时间。
外环测量不受此影响,因此它以周期为单位为您提供真实的总时间。
建议修复
这里有一个简单的helper结构/类,它可以让你在不发生"时间泄漏"的情况下计算各种累加器中的时间。任何时候你调用"split"成员函数,你都必须通过引用给它一个累加器变量,它将在这里累积上一个时间间隔:
struct Timer {
uint64_t _previous_tsc;
Timer() : _previous_tsc(rdtsc()) {}
void split( uint64_t & accumulator )
{
uint64_t tmp = rdtsc();
accumulator += tmp - _previous_tsc;
_previous_tsc = tmp;
}
};
现在,您可以使用一个实例来计时内部循环的"拆分",另一个实例用于整个外部循环:
uint64_t flagtime = 0; // outer loop
uint64_t otherTime = 0; // inner split
uint64_t iterateTime = 0; // inner split
uint64_t loopTime = 0; // inner split
Timer tsc_outer;
Timer tsc_inner;
while(! it2->done()) {
tsc_inner.split( loopTime );
bool cont = it2->Next(); //if we have more in the state table, we continue
if(!cont) break;
totalF2+=it2->value2(); //for experiment, recording the sum of v
updates++; //for experiment, recording the number of updates
tsc_inner.split( otherTime );
run_iter(it2->key(), it2->value1(), it2->value2(), it2->value3());
tsc_inner.split( iterateTime );
}
tsc_outer.split( flagtime );
这是现在"紧"你不会错过任何周期。不过,需要注意的是,它仍然使用RDTSC
而不是RDTSCP
,因此它没有序列化,这意味着您可能仍然在report下报告在一个拆分中花费的时间(如iterateTime),而则在report上报告其他累加器(如loopTimeterateTime中计算的缓存未命中将在loopTime中计算。
注意:虚拟机的虚拟机监控程序可能正在捕获RDTSC
需要注意的一点是,在虚拟机中,当用户级程序试图执行RDTSC
。。。这肯定会使执行串行化,并成为巨大的性能瓶颈。在这些情况下,系统管理程序emulates
执行RDTSC
,并为应用程序提供虚拟时间戳。请参阅SO问题虚拟机上奇怪的程序延迟行为。
最初我认为这不是你观察到的问题,现在我想知道是否是。如果虚拟机实际上捕获了RDTSC
,那么你必须添加硬件的开销,保存VM寄存器,调度内核/系统管理程序,并在"修复"EDX:EAX后恢复你的应用程序以模拟RDTSC
。。。500亿次循环是一个很长的时间,在3GHz下超过16秒。这就解释了为什么你错过了这么多时间。。。110亿周期。。。(44-33)。
- std::vector的包装器,使数组的结构看起来像结构的数组
- 看起来is_nothrow_constructible_v()在MSVC中被破坏了,我错了吗
- 学习多线程C++:添加线程不会使执行速度更快,即使它看起来应该
- 简单C++"Hello World"程序的执行时间长
- 我使用 OpenMP 的线程越多,执行时间就越长,这是怎么回事?
- 尽管一切看起来都很好,但值不会交换
- 自制的上衣:看起来一样,但不完全相同
- 为什么切换 for 循环的顺序会显著改变执行时间?
- cmd.exe与Powershell中C++程序的不同执行时间
- pthread执行时间比顺序执行时间差
- OpenCV 函数 cv::remap() 的执行时间更长,当程序在两者之间进入睡眠状态时
- 为什么 std::chrono 在测量循环和编译器优化的并行 OpenMP 的执行时间时不起作用?
- 我需要帮助来缩短检索 SSL 证书的执行时间
- 如何使它看起来像正在下的雪
- 如何测量cudaMalloc执行时间
- c++中的执行时间和检查流状态
- 为什么for循环中的异步不能提高执行时间
- 为什么 C++ openMP 程序执行时间更长
- C 时间测量看起来太慢了
- 为什么while循环的执行时间看起来如此奇怪