rdtsc, demasiados ciclos
Pregunta
#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;
}
He compilado este código con GCC con -O0 -O1 -O2 -O3 optimizaciones. Y siempre obtengo ciclos 2000-2500. ¿Alguien puede explicar el motivo de esta salida? ¿Cómo pasar estos ciclos?
La primera función "tick" está incorrecta. Esto es correcto.
Otra versión de la función "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 );
}
Este es el código de ensamblaje para -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
Esto es 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
Solución
He probado su código en varias distribuciones de Linux que se ejecutan en diferentes CPU de Intel (sin duda, todos más recientes que el Pentium 4 HT 630 que parece estar usando). En todas esas pruebas obtuve valores entre 25 y 50 ciclos.
Mi única hipótesis que es consistente con toda la evidencia es que está ejecutando su sistema operativo dentro de una máquina virtual en lugar de en metal desnudo, y TSC se está virtualizando.
Otros consejos
Hay muchas razones para obtener un número grande:
- El sistema operativo hizo un cambio de contexto y su proceso se duermió.
- Se produjo una búsqueda en el disco y su proceso se duermió.
- ... Cualquiera de las razones de por qué su proceso podría ser ignorado.
Tenga en cuenta que rdtsc
no es particularmente confiable para el tiempo sin trabajo, porque:
- Las velocidades del procesador pueden cambiar y, por lo tanto, cambia la longitud de un ciclo (cuando se mide en segundos).
- Diferentes procesadores pueden tener diferentes valores para el TSC para un instante dado en el tiempo.
La mayoría de los sistemas de operaciones tienen un reloj o método de tiempo de alta precisión. clock_gettime
En Linux, por ejemplo, particularmente los relojes monotónicos. (Comprenda también la diferencia entre un reloj de pared y un reloj monotónico: un reloj de pared puede moverse hacia atrás, incluso en UTC). En Windows, creo que la recomendación es QueryHighPerformanceCounter
. Por lo general, estos relojes proporcionan una precisión más que suficiente para la mayoría de las necesidades.
Además, mirando la asamblea, parece que solo estás obteniendo 32 bits de la respuesta: no veo %edx
ser salvado después rdtsc
.
Ejecutando su código, obtengo horarios de 120-150 ns para clock_gettime
usando CLOCK_MONOTONIC
, y 70-90 ciclos para RDTSC (~ 20 ns a toda velocidad, pero sospecho que el procesador está registrado, y eso es realmente alrededor de 50 ns). (En un computadora portátilDesktop (maldita sea SSH, olvidé en qué máquina estaba en la que estaba!) Eso está en un uso constante del 20% de CPU) ¿Seguro que su máquina no está empantanada?
Parece que su OS desactivó la ejecución de RDTSC en el espacio de usuario. Y su aplicación tiene que cambiar al kernel y de regreso, lo que requiere muchos ciclos.
Esto es del manual del desarrollador de software Intel:
Cuando está en modo 8086 protegido o virtual, el indicador de sello de tiempo Desactive (TSD) en el registro CR4 restringe el uso de la instrucción RDTSC de la siguiente manera. Cuando el indicador TSD está claro, la instrucción RDTSC se puede ejecutar en cualquier nivel de privilegio; Cuando se establece el indicador, la instrucción solo se puede ejecutar en el nivel de privilegio 0. (cuando en modo de dirección real, la instrucción RDTSC siempre está habilitada).
Editar:
Respondiendo al comentario de Aix, explico, por qué TSD es muy probable que sea el motivo aquí.
Sé solo estas posibilidades para que un programa realice una sola instrucción más tiempo de lo habitual:
- Corriendo bajo algún emulador,
- Uso de código auto-modificado,
- cambio de contexto,
- interruptor de núcleo.
Las primeras 2 razones generalmente no pueden retrasar la ejecución por más de unos pocos cientos de ciclos. Los ciclos 2000-2500 son más típicos para el interruptor de contexto/núcleo. Pero es prácticamente imposible atrapar un cambio de contexto varias veces en el mismo lugar. Por lo tanto, debe ser un interruptor de núcleo. Lo que significa que cualquier programa se ejecuta bajo depurador o RDTSC no está permitido en modo de usuario.
La razón más probable para que OS desactive RDTSC puede ser la seguridad. Hubo intentos de usar los programas de cifrado RDTSC para agrietarse.
Instrucción Cache Miss? (esta es mi suposición)
Además, posiblemente,
¿Cambiar a Hypervisor en un sistema virtualizado? ¿Remnants of Program Bootstrap (incluida la actividad de la red en la misma CPU)?
Para Thanatos: en sistemas más recientes que 2008, RDTSC () es un reloj de pared y no varía con los pasos de frecuencia.
¿Puedes probar este pequeño código?
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;
}
Solo una idea: ¿tal vez estas dos instrucciones RDTSC se ejecutan en diferentes núcleos? Los valores de RDTSC pueden variar ligeramente a través de los núcleos.