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
¿Fue útil?

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:

  1. Corriendo bajo algún emulador,
  2. Uso de código auto-modificado,
  3. cambio de contexto,
  4. 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top