Pregunta

El siguiente fragmento de código se ha dado a nosotros de nuestro instructor para que pudiéramos medir el rendimiento de algunos algoritmos:

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    access_counter(&ncyc_hi, &ncyc_lo);

    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;

    result = (double) hi * (1 << 30) * 4 + lo;

    return result;
}

Sin embargo, necesito el código para ser portátil a máquinas con diferentes frecuencias de la CPU. Por eso, yo estoy tratando de calcular la frecuencia de la CPU de la máquina en la que el código se ejecuta como esto:

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

El problema es que el resultado es siempre 0 y no puedo entender por qué. Estoy corriendo Linux (Arco) como invitado en VMware.

En la máquina de un amigo (MacBook) es trabajar hasta cierto punto; Es decir, el resultado es mayor que 0 pero es la variable debido a que la frecuencia de la CPU no es fijo (tratamos de arreglarlo, pero por alguna razón no somos capaces de hacerlo). Él tiene un equipo diferente que se está ejecutando Linux (Ubuntu) como anfitrión y que también informa 0. Esto elimina el problema de estar en la máquina virtual, por lo que pensé que era el problema al principio.

Cualquier idea por qué está sucediendo esto y cómo puedo solucionarlo?

¿Fue útil?

Solución

De acuerdo, ya que la otra respuesta no fue útil, voy a tratar de explicar en más detalle. El problema es que una CPU moderna puede ejecutar instrucciones fuera de orden. Su código comienza como algo como:

rdtsc
push 1
call sleep
rdtsc

CPUs modernos hacen no necesariamente ejecutar instrucciones en su orden original sin embargo. A pesar de su orden original, la CPU es (casi) libre de ejecutar que al igual que:

rdtsc
rdtsc
push 1
call sleep

En este caso, está claro por qué la diferencia entre los dos rdtscs sería (al menos muy cerca) 0. Para evitar esto, es necesario ejecutar una instrucción que la CPU no rearrange para ejecutar fuera de orden. La instrucción más común de usar para que sea CPUID. La otra respuesta que he vinculado debería (si mal no recuerdo) iniciar o menos a partir de ahí, sobre las medidas necesarias para utilizar correctamente CPUID / eficacia para esta tarea.

Por supuesto, es posible que Tim Post fue bien, y ya está también ver los problemas a causa de una máquina virtual. No obstante, tal y como está en este momento, no hay garantía de que su código funcionará correctamente incluso en hardware real.

Editar: en cuanto a por qué el código lo haría de trabajo: así, en primer lugar, el hecho de que las instrucciones puede se ejecutará fuera de servicio no garantiza que < em> ser. En segundo lugar, es posible que (al menos algunas implementaciones de) sleep contienen instrucciones que impiden rdtsc de ser reorganizado alrededor de ella la serialización, mientras que otros no lo hacen (o pueden contenerlos, pero sólo se ejecutan bajo específico (pero sin especificar) circunstancias).

Lo que te queda es el comportamiento que podría cambiar con casi cualquier re-compilación, o incluso sólo entre una carrera y la siguiente. Se podría producir resultados muy precisos docenas de veces en una fila, luego no para algunos (casi) por completo inexplicable razón (por ejemplo, algo que ocurrió en algún otro proceso en su totalidad).

Otros consejos

No se puede decir con certeza qué es exactamente lo que está mal con su código, pero usted está haciendo un poco de trabajo innecesario para una instrucción tan sencilla. Recomiendo a simplificar su código rdtsc sustancialmente. No es necesario hacer matemáticas de 64 bits lleva a su auto, y que no es necesario para almacenar el resultado de esta operación como un doble. No es necesario utilizar salidas separadas en su asm en línea, se puede decir GCC utilizar EAX y EDX.

Esta es una versión muy simplificada de este código:

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

También se debe considerar la impresión de los valores que vamos a salir de este modo se puede ver si usted está saliendo 0s, o algo más.

En cuanto a VMWare, echar un vistazo a el tiempo de mantenimiento de especificaciones (PDF Link ), así como este hilo . TSC instrucciones están (en función del sistema operativo huésped):

  • pasan directamente al hardware real (PV invitado)
  • Count ciclos mientras de la máquina virtual está ejecutando en el procesador anfitrión (Windows / etc)

Tenga en cuenta, en el # 2 mientras de la máquina virtual está ejecutando en el procesador anfitrión. El mismo fenómeno se iría para Xen, así, si no recuerdo mal. En esencia, se puede esperar que el código debería funcionar como se espera en un huésped para-virtualizado. Si emulado, íntegramente y poco razonable esperar que el hardware como la coherencia.

olvidó de uso volatile en su declaración asm , por lo que está diciendo al compilador que la declaración asm produce el mismo resultado cada vez, como una función pura. (volatile sólo es implícita para los estados asm sin salidas.)

Esto explica por qué recibe exactamente cero:. El compilador optimizado end-start a 0 en tiempo de compilación, a través del CSE (eliminación común subexpresión)

Ver mi respuesta en Get recuento de ciclos de CPU? para la intrínseca __rdtsc() y la respuesta de @ Mysticial no tiene trabajo GNU C asm en línea, que voy a citar aquí:

// prefer using the __rdtsc() intrinsic instead of inline asm at all.
uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

Esto funciona correcta y eficiente para el código 32 y 64 bits.

hmmm no soy positivo, pero sospecho que el problema puede estar dentro de esta línea:

resultado = (doble) hi * (1 << 30) * 4 + Mín;

Soy sospechoso si se puede llevar a cabo con seguridad tan grandes multiplicaciones en un "sin firmar" ... no es que a menudo un número de 32 bits? ... sólo el hecho de que no se podía con seguridad se multiplican por 2 ^ 32 y tuvo que anexe como un extra "* 4" añadido a la 2 ^ 30 en el extremo ya se alude a esta posibilidad ... es posible que necesite convertir cada hi sub-componente y lo a un doble (en lugar de uno solo al final) y hacer la multiplicación utilizando los dos dobles

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