我想用 rdtsc 计时函数调用。所以我用以下两种方式测量它。
- 循环调用它。聚合循环内的每个 rdtsc 差异并除以调用次数。(假设这是N)
- 循环调用它。获取循环本身的rdtsc差异并除以N。
但我看到了一些不一致的行为。
- 当我增加 N 时,方法 1 和 2 中的时间都会相当单调地减少。对于方法 2,它可以分摊循环控制开销是可以理解的。但我不确定方法 1 的情况如何。
- 实际上,对于方法 2,每次增加 N 时,我得到的 N=1 的值似乎每次都除以新的 N。检查 gdb 反汇编让我意识到这是 -O2 的一些编译器优化,在第二种情况下跳过了循环。所以我用-O0重试了,gdb反汇编显示了第二种情况的实际循环。
代码如下。
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
typedef unsigned long long ticks;
static __inline__ ticks getticks(void) {
unsigned a, d;
asm volatile("rdtsc" : "=a" (a), "=d" (d));
return ((ticks)a) | (((ticks)d) << 32);
}
__attribute__ ((noinline))
void bar() {
}
int main(int argc, char** argv) {
long long N = 1000000;
N = atoi(argv[1]);
int i;
long long bar_total = 0;
ticks start = 0, end = 0;
for (i = 0; i < N; i++) {
start = getticks();
bar();
end = getticks();
bar_total += (end - start);
}
fprintf(stdout, "Total invocations : %lld\n", N);
fprintf(stdout, "[regular] bar overhead : %lf\n", ((double)bar_total/ N));
start = getticks();
for (i = 0; i < N; i++) {
bar();
}
end = getticks();
bar_total = (end - start);
fprintf(stdout, "[Loop] bar overhead : %lf\n", ((double)bar_total/ N));
return 0;
}
知道这里发生了什么吗?如果需要,我也可以将 gdb 反汇编。我使用了来自http://dasher.wustl.edu/tinker/distribution/fftw/kernel/cycle.h的 rdtsc 实现
编辑: 我将不得不撤回我的第二个声明,即在 -O0 处,在第二种情况下,时间与 N 成正比下降。我想这是我在构建过程中犯的一些错误,导致一些旧版本持续存在。无论如何它仍然与方法1的数字一起下降。这里是不同N值的一些数字。
taskset -c 2 ./example.exe 1
Total invocations : 1
[regular] bar overhead : 108.000000
[Loop] bar overhead : 138.000000
taskset -c 2 ./example.exe 10
Total invocations : 10
[regular] bar overhead : 52.900000
[Loop] bar overhead : 40.700000
taskset -c 2 ./example.exe 100
Total invocations : 100
[regular] bar overhead : 46.780000
[Loop] bar overhead : 15.570000
taskset -c 2 ./example.exe 1000
Total invocations : 1000
[regular] bar overhead : 46.069000
[Loop] bar overhead : 13.669000
taskset -c 2 ./example.exe 100000
Total invocations : 10000
[regular] bar overhead : 46.010100
[Loop] bar overhead : 13.444900
taskset -c 2 ./example.exe 100000000
Total invocations : 100000000
[regular] bar overhead : 26.970272
[Loop] bar overhead : 5.201252
taskset -c 2 ./example.exe 1000000000
Total invocations : 1000000000
[regular] bar overhead : 18.853279
[Loop] bar overhead : 5.218234
taskset -c 2 ./example.exe 10000000000
Total invocations : 1410065408
[regular] bar overhead : 18.540719
[Loop] bar overhead : 5.216395
我现在看到了两种新行为。
- 方法 1 的收敛速度比方法 2 慢。但我仍然对为什么不同 N 设置的值存在如此大的差异感到困惑。也许我在这里犯了一些我目前看不到的基本错误。
- 方法 1 的值实际上比方法 2 大一些。我预计它与方法 2 的值持平或略小,因为它不包含循环控制开销。
问题
总而言之,我的问题是
为什么增加 N 时两种方法给出的值变化如此之大?特别适用于不考虑循环控制开销的方法 1。
当第一种方法在计算中排除循环控制开销时,为什么第二种方法的结果小于第一种方法的结果?
编辑 2
关于建议的 rdtscp 解决方案。
由于对内联汇编不熟悉,我做了以下事情。
static __inline__ ticks getstart(void) {
unsigned cycles_high = 0, cycles_low = 0;
asm volatile ("CPUID\n\t"
"RDTSC\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" (cycles_low)::
"%rax", "%rbx", "%rcx", "%rdx");
return ((ticks)cycles_high) | (((ticks)cycles_low) << 32);
}
static __inline__ ticks getend(void) {
unsigned cycles_high = 0, cycles_low = 0;
asm volatile("RDTSCP\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
"CPUID\n\t": "=r" (cycles_high), "=r" (cycles_low)::
"%rax", "%rbx", "%rcx", "%rdx");
return ((ticks)cycles_high) | (((ticks)cycles_low) << 32);
}
并在函数调用之前和之后使用上述方法。但现在我得到如下无意义的结果。
Total invocations : 1000000
[regular] bar overhead : 304743228324.708374
[Loop] bar overhead : 33145641307.734016
有什么问题?我想将它们分解为内联方法,因为我看到在多个地方使用它。
A. 评论中的解决方案。