我正在做一些Linux内核计时,特别是在中断处理路径中。我一直在使用rdtsc来计时,但是最近我了解到它并不一定准确,因为指令可能会发生故障。
然后我试着:
rdtsc+cpuid(按相反的顺序,此处)来刷新管道,并产生高达60倍的开销(!)在虚拟机(我的工作环境)上,由于超调用等原因。无论是否启用了硬件虚拟化,都是如此。
最近我遇到了rdtscp*指令,它看起来像rdtsc+cpuid所做的那样,但是由于它是一个较新的指令,所以效率更高-相对而言,只有1.5x-2x的开销。
我的问题是:rdtscp作为一个测量点是否真的准确,它是进行计时的“正确”方式吗?
更明确地说,我的时间安排基本上是这样的,在内部:
保存当前周期计数器值
执行一种基准测试(即磁盘、网络)
将当前和上一个周期计数器的增量加到累加器值,并在每个中断中增加一个计数器
最后,将增量/累加器除以中断数,得到每个中断的平均周期成本。
*http://www.intel.de/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf第27页
最佳答案
有关cpuid指令的开销的详细讨论,请访问this stackoverflow thread。使用rdtsc时,需要使用cpuid来确保执行管道中没有其他指令。rdtscp指令从本质上刷新管道。(引用的so线程也讨论了这些要点,但我之所以在这里讨论这些要点,是因为它们也是您问题的一部分)。
如果您的处理器不支持rdtscp,您只需要使用cpuid+rdtsc。否则,rdtscp就是您想要的,并将准确地为您提供所需的信息。
这两条指令都为您提供一个64位、单调递增的计数器,表示处理器上的周期数。如果这是你的模式:
uint64_t s, e;
s = rdtscp();
do_interrupt();
e = rdtscp();
atomic_add(e - s, &acc);
atomic_add(1, &counter);
根据你的阅读发生的地点,你的平均测量值可能还是会有一个差。例如:
T1 T2
t0 atomic_add(e - s, &acc);
t1 a = atomic_read(&acc);
t2 c = atomic_read(&counter);
t3 atomic_add(1, &counter);
t4 avg = a / c;
现在还不清楚“[a]t the end”是否指的是一个可以以这种方式竞争的时间。如果是这样的话,你可能需要根据你的delta来计算一个运行平均值或一个移动平均值。
侧重点:
如果使用cpuid+rdtsc,则需要减去cpuid指令的开销,这可能很难确定您是否在vm中(取决于vm如何实现此指令)。这就是你应该坚持RDTSCP的真正原因。
在循环中执行rdtscp通常是个坏主意。我经常看到一些微市场做一些类似的事情
——
for (int i = 0; i < SOME_LARGEISH_NUMBER; i++) {
s = rdtscp();
loop_body();
e = rdtscp();
acc += e - s;
}
printf("%"PRIu64"\n", (acc / SOME_LARGEISH_NUMBER / CLOCK_SPEED));
虽然这会让您对
loop_body()
中的任何循环的总体性能有一个很好的了解,但它会挫败处理器优化,例如流水线。在微标号中,处理器将在循环中做好分支预测,因此测量循环开销是很好的。这样做也不好,因为每次循环迭代都有两个管道暂停。因此:s = rdtscp();
for (int i = 0; i < SOME_LARGEISH_NUMBER; i++) {
loop_body();
}
e = rdtscp();
printf("%"PRIu64"\n", ((e-s) / SOME_LARGEISH_NUMBER / CLOCK_SPEED));
与之前的基准相比,在实际生活中看到的东西会更有效率,可能会更准确。
关于c - RDTSCP与RDTSC + CPUID,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27693145/