题
#include <stdio.h>
static inline unsigned long long tick()
{
unsigned long long d;
__asm__ __volatile__ ("rdtsc" : "=A" (d) );
return d;
}
int main()
{
long long res;
res=tick();
res=tick()-res;
printf("%d",res);
return 0;
}
我已经用-O0 -O1 -O2 -O3优化将GCC编译为GCC。而且我总是得到2000-2500周期。谁能解释该输出的原因?如何花这些周期?
第一个功能“ tick”是错误的。这是正确的.
另一个版本的功能“ tick”
static __inline__ unsigned long long tick()
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
这是-O3的汇编代码
.file "rdtsc.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl %ecx, -16(%ebp)
movl %ebx, -12(%ebp)
movl %esi, -8(%ebp)
movl %edi, -4(%ebp)
#APP
# 6 "rdtsc.c" 1
rdtsc
# 0 "" 2
#NO_APP
movl %edx, %edi
movl %eax, %esi
#APP
# 6 "rdtsc.c" 1
rdtsc
# 0 "" 2
#NO_APP
movl %eax, %ecx
movl %edx, %ebx
subl %esi, %ecx
sbbl %edi, %ebx
movl %ecx, 4(%esp)
movl %ebx, 8(%esp)
movl $.LC0, (%esp)
call printf
movl -16(%ebp), %ecx
xorl %eax, %eax
movl -12(%ebp), %ebx
movl -8(%ebp), %esi
movl -4(%ebp), %edi
movl %ebp, %esp
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Debian 4.3.2-1.1) 4.3.2"
.section .note.GNU-stack,"",@progbits
这是CPU
processor : 0
vendor_id : GenuineIntel
cpu family : 15
model : 4
model name : Intel(R) Xeon(TM) CPU 3.00GHz
stepping : 3
cpu MHz : 3000.105
cache size : 2048 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 5
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss constant_tsc up pebs bts pni
bogomips : 6036.62
clflush size : 64
解决方案
我已经在不同的Intel CPU上运行的几个Linux发行版上尝试了您的代码(诚然,与您似乎正在使用的Pentium 4 HT 630相比,所有的代码更新)。在所有这些测试中,我得到的值在25至50个周期之间。
我唯一与所有证据一致的假设是,您在虚拟机内而不是在裸机上运行操作系统,而TSC正在虚拟化。
其他提示
有很多理由获得大量的原因:
- 操作系统进行了上下文开关,并且您的过程入睡。
- 发生了一个磁盘,您的过程入睡。
- …关于为什么您的过程可能会被忽略的任何原因。
注意 rdtsc
对于没有工作的时间并不是特别可靠的,因为:
- 处理器速度可能会改变,因此,周期的长度(以秒为单位测量)会发生变化。
- 对于给定的瞬间,不同的处理器可能具有不同的TSC值。
大多数操作系统都有高精度的时钟或时机方法。 clock_gettime
例如,在Linux上,尤其是单调时钟。 (也了解墙壁锁和单调时钟之间的区别:壁钟可以向后移动,即使在UTC中也可以移动。)在窗户上,我认为建议是 QueryHighPerformanceCounter
. 。通常,这些时钟为大多数需求提供足够的精度。
另外,看大会,看来您只得到32位答案:我看不到 %edx
得到保存 rdtsc
.
运行您的代码,我从120-150 ns获得时间安排 clock_gettime
使用 CLOCK_MONOTONIC
, ,RDTSC的70-90循环(全速约20 ns,但我怀疑处理器被计入,这实际上是大约50 ns)。 (在 笔记本电脑桌面(DARN SSH,忘记了我使用的台机器!)大约使用20%的CPU)确保您的机器不会陷入困境?
看起来您在用户空间中禁用RDTSC的操作系统执行。而且您的应用程序必须切换到内核和后背,这需要很多周期。
这是来自英特尔软件开发人员的手册:
在受保护或虚拟8086模式下,寄存器CR4中的时间戳禁用(TSD)标志限制了RDTSC指令的使用如下。当明确TSD标志时,可以在任何特权级别执行RDTSC指令;设置标志时,只能在特权级别0处执行指令(在实地模式下,始终启用RDTSC指令。)
编辑:
我解释说,回答AIX的评论,为什么TSD很可能是这里的原因。
我只知道这些程序可以比平常执行单个指令更长的可能性:
- 在某些模拟器下运行,
- 使用自我修饰的代码,
- 上下文开关,
- 内核开关。
前两个原因通常不能延迟执行超过几百个周期。 2000-2500周期更典型地用于上下文/内核开关。但是,几乎不可能在同一位置几次捕获上下文切换。因此应该是内核开关。这意味着在用户模式下不允许使用调试器或RDTSC下的任何一个程序。
OS禁用RDTSC的最可能原因可能是安全性。试图使用RDTSC破解加密程序。
指令缓存错过? (这是我的猜测)
而且,可能,
切换到虚拟化系统中的管理程序?程序引导程序的残余(包括同一CPU上的网络活动)?
到Thanatos:在最近的系统中,RDTSC()是一个壁钟,并且不随频率步骤而变化。
您可以尝试这个小代码吗?
int main()
{
long long res;
fflush(stdout); // chnage the exact timing of stdout, in case there is something to write in a ssh connection, together with its interrupts
for (int pass = 0; pass < 2; pass++)
{
res=tick();
res=tick()-res;
}
printf("%d",res); // ignore result on first pass, display the result on second pass.
return 0;
}
只是一个想法 - 也许这两个RDTSC说明在不同的内核上执行? RDTSC值在核心之间可能略有变化。