我想用rdtsc计时函数调用。所以我用两种方式测量它如下。
但我看到一些不一致的行为。
代码如下。
#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成正比,与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
我现在看到两个新的行为。
问题
总而言之,我的问题是
为什么增加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
有什么收获?我想把它们当作内联方法,因为我看到它在多个地方使用它。
一个。评论中的解决方案。
答案 0 :(得分:2)
使用普通rdtsc
指令,这些指令可能无法在无序CPU(如Xeon和Core)上正常工作。您应该添加一些序列化指令或切换到rdtscp
instruction:
http://en.wikipedia.org/wiki/Time_Stamp_Counter
从Pentium Pro开始,英特尔处理器支持无序执行,其中指令不一定按照它们在可执行文件中出现的顺序执行。这可能导致RDTSC的执行时间晚于预期,从而产生误导性的循环计数。[3]这个问题可以通过执行序列化指令(如CPUID)来解决,以便在允许程序继续之前强制完成每个前面的指令,或者使用RDTSCP指令,这是RDTSC指令的序列化变体。
英特尔最近使用rdtsc / rdtscp的手册 - How to Benchmark Code Execution Times on Intel IA-32 and IA-64 Instruction Set Architectures(ia-32-ia-64-benchmark-code-execution-paper.pdf,324264-001,2010)。他们建议使用cpuid + rdtsc作为开始,使用rdtscp作为结束计时器:
第0节中提出的问题的解决方案是添加CPUID指令 就在
RDTPSCP
和两个mov
指令之后(存储在内存中) 值edx
和eax
)。实施如下:
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");
/***********************************/
/*call the function to measure here*/
/***********************************/
asm volatile("RDTSCP\n\t"
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
"CPUID\n\t": "=r" (cycles_high1), "=r" (cycles_low1)::
"%rax", "%rbx", "%rcx", "%rdx");
start = ( ((uint64_t)cycles_high << 32) | cycles_low );
end = ( ((uint64_t)cycles_high1 << 32) | cycles_low1 );
在上面的代码中,第一个
CPUID
调用实现了一个屏障,以避免无序 执行RDTSC
指令上方和下方的指令。 然而,这个调用不会影响测量,因为它来自之前RDTSC
(即,在读取时间戳寄存器之前)。 然后,第一个RDTSC
读取时间戳寄存器,并将值存储在中 记忆。 然后执行我们要测量的代码。如果代码是对a的调用 函数,建议声明这样的函数为“inline
”,以便从一个 汇编透视调用函数本身没有开销。RDTSCP
指令第二次读取时间戳寄存器 保证完成我们想要测量的所有代码的执行。
你的例子不是很正确;您尝试测量空函数bar()
,但它很短,您正在测量方法1(for() { rdtsc; bar(); rdtsc)
)中的rdtsc开销。根据Agner Fog关于haswell的表 - http://www.agner.org/optimize/instruction_tables.pdf第191页(长表“Intel Haswell指令时序列表和μop故障”,在最后)
RDTSC
有15个uop(不可融合)和24个滴答的延迟; RDTSCP
(对于较旧的微体系结构,Sandy Bridge有23个uops和36个滴答延迟,而21个uops和28个滴答用于rdtsc)。所以,你不能使用普通的rdtsc(或rdtscp)直接测量这样的短代码。
答案 1 :(得分:1)
你试过clock_gettime(CLOCK_MONOTONIC, &tp)
吗?应该非常接近手动读取循环计数器,还要记住,循环计数器可能不会在cpu核心之间同步。