2008-12-11 22 views
104

Al usar solo ANSI C, ¿hay alguna forma de medir el tiempo con precisión de milisegundos o más? Estaba navegando por time.h pero solo encontré funciones de segunda precisión.¿Cómo medir el tiempo en milisegundos usando ANSI C?

+4

Tenga en cuenta la diferencia entre precisión y precisión.Puede obtener un tiempo con milisegundos * de precisión * tomando el tiempo en segundos y multiplicando por 1000, pero eso no sirve de nada. Las funciones de precisión ms no tienen necesariamente más precisión, aunque en general tienen una precisión superior a 1s. –

+2

La respuesta simple es NO, ANSI C no admite una precisión de milisegundos o superior. La respuesta más compleja depende de lo que estás tratando de hacer, francamente toda el área es una pesadilla, incluso si permites el uso de las funciones de Posix ampliamente disponibles. Usas el término "medida" así que asumo que estás interesado en un intervalo en vez de un "reloj de pared". ¿Pero está intentando medir un período de tiempo absoluto o el uso de la CPU por su proceso? – Dipstick

+0

Sólo quería decir a SOF que acaba de guardar mi tocino, otra vez ;-) – corlettk

Respuesta

79

No existe una función ANSI C que proporcione una resolución de tiempo superior a 1 segundo, pero la función POSIX gettimeofday proporciona una resolución de microsegundos. La función de reloj solo mide la cantidad de tiempo que un proceso ha estado ejecutando y no es precisa en muchos sistemas.

Se puede utilizar esta función como esta:

struct timeval tval_before, tval_after, tval_result; 

gettimeofday(&tval_before, NULL); 

// Some code you want to time, for example: 
sleep(1); 

gettimeofday(&tval_after, NULL); 

timersub(&tval_after, &tval_before, &tval_result); 

printf("Time elapsed: %ld.%06ld\n", (long int)tval_result.tv_sec, (long int)tval_result.tv_usec); 

Esto devuelve Time elapsed: 1.000870 en mi máquina.

+22

Advertencia leve: gettimeofday() no es monótona, lo que significa que puede saltar (e incluso retroceder) si, por ejemplo, su máquina intenta mantener la sincronización con un servidor de tiempo de red o alguna otra fuente de tiempo. – Dipstick

+3

Para ser precisos: en ISO C99 (que creo que es compatible en esta parte con ANSI C), ni siquiera hay una garantía de * cualquier * resolución de tiempo. (ISO C99, 7.23.1p4) –

+10

Un pequeño ejemplo de uso hubiera sido tan agradable ... –

4

La mejor precisión que puede obtener es mediante el uso de la instrucción "rdtsc" solo x86, que puede proporcionar resolución de reloj (por supuesto, debe tener en cuenta el costo de la llamada rdtsc, que puede medirse fácilmente al inicio de la aplicación).

El principal inconveniente aquí es medir el número de relojes por segundo, lo que no debería ser demasiado difícil.

+3

Es posible que también deba preocuparse por la afinidad del procesador, porque en algunas máquinas puede enviar las llamadas RDTSC a más de un procesador y su RDTSC los contadores pueden no estar sincronizados. –

+1

Y, además, algunos procesadores no tienen un TSC monótonamente creciente: piense en modos de ahorro de energía que reducen la frecuencia de la CPU. El uso de RDTSC para todo menos * muy * cortos tiempos localizados es una ** mala idea ** **. – snemarch

+0

Por cierto, la deriva del núcleo mencionada por @WillDean y el uso de rdtsc para el tiempo es la razón por la que varios juegos no funcionaron en (temprano?) CPU AMD64 multinúcleo. Tuve que limitarme a la afinidad de un solo núcleo en mi x2 4400+ para una cantidad de títulos. – snemarch

42
#include <time.h> 
clock_t uptime = clock()/(CLOCKS_PER_SEC/1000); 
+0

CLOCKS_PER_SEC está establecido en 1000000 en muchos sistemas. Imprima su valor para estar seguro antes de usarlo de esta manera. – ysap

+13

Como son relojes por segundo, no importa qué valor tenga, el valor resultante de clock()/CLOCKS_PER_SEC será en segundos (al menos debería ser). Dividir por 1000 lo convierte en milisegundos. –

+11

De acuerdo con el Manual de referencia de C, los valores de clock_t pueden ajustarse a partir de alrededor de 36 minutos. Si está midiendo un cómputo largo, debe ser consciente de esto. – CyberSkull

25

Siempre uso la función clock_gettime(), regresando la hora desde el reloj CLOCK_MONOTONIC. El tiempo devuelto es la cantidad de tiempo, en segundos y nanosegundos, desde algún punto no especificado en el pasado, como el inicio del sistema de la época.

#include <stdio.h> 
#include <stdint.h> 
#include <time.h> 

int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p) 
{ 
    return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) - 
      ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec); 
} 

int main(int argc, char **argv) 
{ 
    struct timespec start, end; 
    clock_gettime(CLOCK_MONOTONIC, &start); 

    // Some code I am interested in measuring 

    clock_gettime(CLOCK_MONOTONIC, &end); 

    uint64_t timeElapsed = timespecDiff(&end, &start); 
} 
+5

clock_gettime() no es ANSI C. – PowerApp101

+1

También CLOCK_MONOTONIC no está implementado en muchos sistemas (incluidas muchas plataformas Linux). – Dipstick

+1

@ PowerApp101 No existe una forma buena/robusta ANSI C de hacer esto. Muchas de las otras respuestas se basan en POSIX en lugar de ANCI C. Dicho esto, creo que hoy en día. @Dipstick Hoy en día, creo que la mayoría de las plataformas modernas [cita requerida] admiten 'clock_gettime (CLOCK_MONOTONIC, ...)' y hay incluso la macro de prueba de características '_POSIX_MONOTONIC_CLOCK'. – omninonsense

-3

bajo Windows:

SYSTEMTIME t; 
GetLocalTime(&t); 
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds); 
+0

es esta ansi C según lo solicitado? – Gyom

+0

@Gyom, por supuesto, no lo es. –

+0

Por supuesto que no, es para Windows :-) – ThatAintWorking

4

timespec_get de C11 rendimientos hasta nanosegundos, redondeado a la resolución de la aplicación.

Parece una estafa ANSI de POSIX 'clock_gettime.

Ejemplo: un printf se realiza cada 100 ms en Ubuntu 15.10:

#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 

static long get_nanos(void) { 
    struct timespec ts; 
    timespec_get(&ts, TIME_UTC); 
    return (long)ts.tv_sec * 1000000000L + ts.tv_nsec; 
} 

int main(void) { 
    long nanos; 
    long last_nanos; 
    long start; 
    nanos = get_nanos(); 
    last_nanos = nanos; 
    start = nanos; 
    while (1) { 
     nanos = get_nanos(); 
     if (nanos - last_nanos > 100000000L) { 
      printf("current nanos: %ld\n", nanos - start); 
      last_nanos = nanos; 
     } 
    } 
    return EXIT_SUCCESS; 
} 

El C11 N1570 standard draft7.27.2.5 El timespec_get función dice:

Si base es TIME_UTC, el miembro tv_sec es establecer el número de segundos desde una implementación de definida época, truncado a un valor completo y el miembro tv_nsec es establecido en el número integral de nanosegundos, redondeado a la resolución del reloj del sistema. (321)

321) Aunque un objeto struct timespec describe los tiempos con una resolución de nanosegundos, la resolución disponible depende del sistema e incluso puede ser mayor a 1 segundo.

C++ 11 también tiene std::chrono::high_resolution_clock: C++ Cross-Platform High-Resolution Timer

glibc 2.21 implementa bajo sysdeps/posix/timespec_get.c como:

int 
timespec_get (struct timespec *ts, int base) 
{ 
    switch (base) 
    { 
    case TIME_UTC: 
     if (__clock_gettime (CLOCK_REALTIME, ts) < 0) 
     return 0; 
     break; 

    default: 
     return 0; 
    } 

    return base; 
} 

tan claramente:

  • actualmente sólo TIME_UTC está soportado

  • hacia delante a __clock_gettime (CLOCK_REALTIME, ts), que es un API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    Linux x86-64 tiene un sistema clock_gettime llamada.

    Tenga en cuenta que esto no es un método a prueba de fallos micro-evaluación comparativa porque:

    • man clock_gettime dice que esta medida puede tener discontinuidades si cambia algún tiempo, mientras que su sistema de ajuste de programa se ejecuta. Este debería ser un evento raro, por supuesto, y es posible que puedas ignorarlo.

    • esto mide el tiempo de pared, por lo que si el planificador decide olvidarse de su tarea, parecerá que se ejecuta durante más tiempo.

    Por estas razones getrusage() podría ser una mejor herramienta mejor POSIX evaluación comparativa, a pesar de su menor precisión máxima microsegundo.

    Más información en: Measure time in Linux - time vs clock vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?

+0

esta es la respuesta correcta a partir de 2017, incluso MSVC tiene esta función; en términos de evaluación comparativa, busque algo que lea el registro de chips (versiones más recientes de procesadores x86 con extensiones PT y las versiones más nuevas correspondientes de Linux kernel/perf) –

10

La implementación de una solución portátil

Como ya se mencionó aquí que no hay una solución adecuada ANSI con la suficiente precisión para el problema de la medición del tiempo, quiero escribir sobre la forma de obtener una solución portátil y, si es posible, una solución de medición de tiempo de alta resolución.

reloj Monotónica vs marcas de tiempo

en términos generales hay dos maneras de medición del tiempo:

  • reloj monótona;
  • marca de hora actual (fecha).

El primero utiliza un contador de reloj monótona (a veces se le llama un contador de paso) que cuenta garrapatas con una frecuencia predefinida, por lo que si usted tiene un valor garrapatas y se conoce la frecuencia, usted puede convertir fácilmente las garrapatas a tiempo transcurrido. En realidad, no está garantizado que un reloj monotónico refleje la hora actual del sistema de ninguna manera, también puede contar tics desde el inicio de un sistema. Pero garantiza que siempre se ejecute un reloj de manera creciente independientemente del estado del sistema. Por lo general, la frecuencia está ligada a una fuente de alta resolución de hardware, por eso proporciona una alta precisión (depende del hardware, pero la mayoría del hardware moderno no tiene problemas con las fuentes de reloj de alta resolución).

La segunda forma proporciona un valor de tiempo (fecha) basado en el valor actual del reloj del sistema.También puede tener una resolución alta, pero tiene un inconveniente importante: este tipo de valor de tiempo puede verse afectado por diferentes ajustes de tiempo del sistema, es decir, cambio de zona horaria, cambio de horario de verano (DST), actualización del servidor NTP, hibernación del sistema y en. En algunas circunstancias, puede obtener un valor de tiempo transcurrido negativo que puede conducir a un comportamiento indefinido. En realidad, este tipo de fuente de tiempo es menos confiable que el primero.

Por lo tanto, la primera regla en la medición del intervalo de tiempo es usar un reloj monotónico si es posible. Por lo general, tiene una alta precisión y es confiable por diseño.

estrategia de repliegue

Al implementar una solución portátil que vale la pena considerar una estrategia de repliegue: utilizar un reloj monótona si está disponible y vuelve a los sellos enfoque de tiempo si no hay reloj monotónico en el sistema.

de Windows

Hay un gran artículo llamado Acquiring high-resolution time stamps en MSDN sobre la medición del tiempo en Windows que describe todos los detalles que pueda necesitar saber sobre software y soporte de hardware. Para adquirir un sello de tiempo de alta precisión en Windows debe:

  • consulta una frecuencia temporizador (ticks por segundo) con QueryPerformanceFrequency:

    LARGE_INTEGER tcounter; 
    LARGE_INTEGER freq;  
    
    if (QueryPerformanceFrequency (&tcounter) != 0) 
        freq = tcounter.QuadPart; 
    

    La frecuencia del temporizador se fija en el arranque del sistema por lo que necesita para obtenerlo solo una vez

  • consulta el valor actual de las garrapatas con QueryPerformanceCounter:

    escala
    LARGE_INTEGER tcounter; 
    LARGE_INTEGER tick_value; 
    
    if (QueryPerformanceCounter (&tcounter) != 0) 
        tick_value = tcounter.QuadPart; 
    
  • las garrapatas a tiempo transcurrido, es decir, a microsegundos:

    LARGE_INTEGER usecs = (tick_value - prev_tick_value)/(freq/1000000); 
    

De acuerdo con Microsoft que no debería tener ningún problemas con este enfoque en Windows XP y versiones posteriores en la mayoría de los casos. Pero también se puede utilizar dos soluciones de repliegue en Windows:

  • GetTickCount proporciona el número de milisegundos que han transcurrido desde el arranque del sistema. Se envuelve cada 49,7 días, así que tenga cuidado al medir intervalos más largos.
  • GetTickCount64 es una versión de 64 bits de GetTickCount, pero está disponible a partir de Windows Vista y superior.

OS X (macOS)

OS X (macOS) tiene sus propias unidades de tiempo absoluta Mach que representan un reloj monotónica. La mejor manera de comenzar es el artículo de Apple Technical Q&A QA1398: Mach Absolute Time Units que describe (con los ejemplos de código) cómo usar la API específica de Mach para obtener tics monótonos. También hay una pregunta local al respecto llamada clock_gettime alternative in Mac OS X que al final puede dejarle un poco confundido sobre qué hacer con el posible desbordamiento de valor porque la frecuencia del contador se usa en forma de numerador y denominador.Por lo tanto, un breve ejemplo cómo conseguir tiempo transcurrido:

  • obtener el numerador y el denominador frecuencia de reloj:

    #include <mach/mach_time.h> 
    #include <stdint.h> 
    
    static uint64_t freq_num = 0; 
    static uint64_t freq_denom = 0; 
    
    void init_clock_frequency() 
    { 
        mach_timebase_info_data_t tb; 
    
        if (mach_timebase_info (&tb) == KERN_SUCCESS && tb.denom != 0) { 
         freq_num = (uint64_t) tb.numer; 
         freq_denom = (uint64_t) tb.denom; 
        } 
    } 
    

    Es necesario hacer que sólo una vez.

  • consulta el valor actual garrapata con mach_absolute_time:

    escala
    uint64_t tick_value = mach_absolute_time(); 
    
  • las garrapatas a tiempo transcurrido, es decir, a microsegundos, utilizando numerador previamente consultada y el denominador:

    uint64_t value_diff = tick_value - prev_tick_value; 
    
    /* To prevent overflow */ 
    value_diff /= 1000; 
    
    value_diff *= freq_num; 
    value_diff /= freq_denom; 
    

    La idea principal de evitar un desbordamiento es reducir las marcas a la precisión deseada antes de usar el numerador y el denominador. Como la resolución del temporizador inicial es en nanosegundos, la dividimos por 1000 para obtener microsegundos. Puede encontrar el mismo enfoque utilizado en Chromium's time_mac.c. Si realmente necesita una precisión de nanosegundos, considere leer el How can I use mach_absolute_time without overflowing?.

Linux y UNIX

La llamada clock_gettime es la mejor manera en cualquier sistema POSIX ambiente. Puede consultar el tiempo de diferentes fuentes de reloj, y el que necesitamos es CLOCK_MONOTONIC. No todos los sistemas que tienen clock_gettime apoyo CLOCK_MONOTONIC, así que lo primero que tiene que hacer es comprobar su disponibilidad:

  • si _POSIX_MONOTONIC_CLOCK se define a un valor >= 0 significa que CLOCK_MONOTONIC sido desactivada;
  • si _POSIX_MONOTONIC_CLOCK se define a 0 que significa que usted debe comprobar, además, si funciona en tiempo de ejecución, mejor utilizar sysconf:

    #include <unistd.h> 
    
    #ifdef _SC_MONOTONIC_CLOCK 
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) { 
        /* A monotonic clock presents */ 
    } 
    #endif 
    
  • de lo contrario un reloj monótona no es compatible y se debe utilizar una estrategia de repliegue (vea abajo).

Uso de clock_gettime es bastante sencillo:

  • obtener el valor de tiempo:

    #include <time.h> 
    #include <sys/time.h> 
    #include <stdint.h> 
    
    uint64_t get_posix_clock_time() 
    { 
        struct timespec ts; 
    
        if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0) 
         return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec/1000); 
        else 
         return 0; 
    } 
    

    He reducido el tiempo de microsegundos aquí.

  • calcular la diferencia con el valor anterior vez recibida la misma manera:

    uint64_t prev_time_value, time_value; 
    uint64_t time_diff; 
    
    /* Initial time */ 
    prev_time_value = get_posix_clock_time(); 
    
    /* Do some work here */ 
    
    /* Final time */ 
    time_value = get_posix_clock_time(); 
    
    /* Time difference */ 
    time_diff = time_value - prev_time_value; 
    

La mejor estrategia de repliegue es usar la llamada gettimeofday: no es una monótona, sino que proporciona un buen buena resolución.La idea es la misma que con clock_gettime, pero para obtener un valor de tiempo debe:

#include <time.h> 
#include <sys/time.h> 
#include <stdint.h> 

uint64_t get_gtod_clock_time() 
{ 
    struct timeval tv; 

    if (gettimeofday (&tv, NULL) == 0) 
     return (uint64_t) (tv.tv_sec * 1000000 + tv.tv_usec); 
    else 
     return 0; 
} 

Una vez más, el valor de tiempo es reducido a microsegundos.

SGI IRIX

IRIX tiene la llamada clock_gettime, pero carece de CLOCK_MONOTONIC. En cambio, tiene su propia fuente de reloj monotónica definida como CLOCK_SGI_CYCLE que debe usar en lugar de CLOCK_MONOTONIC con clock_gettime.

Solaris y HP-UX

Solaris tiene su propia interfaz temporizador de alta resolución gethrtime que devuelve el valor actual del temporizador en nanosegundos. Aunque las versiones más recientes de Solaris pueden tener clock_gettime, puede mantener el gethrtime si necesita admitir versiones anteriores de Solaris.

uso es simple:

#include <sys/time.h> 

void time_measure_example() 
{ 
    hrtime_t prev_time_value, time_value; 
    hrtime_t time_diff; 

    /* Initial time */ 
    prev_time_value = gethrtime(); 

    /* Do some work here */ 

    /* Final time */ 
    time_value = gethrtime(); 

    /* Time difference */ 
    time_diff = time_value - prev_time_value; 
} 

HP-UX carece clock_gettime, pero soporta gethrtime que se debe utilizar en la misma forma que en Solaris.

BeOS

BeOS también tiene su propia interfaz de temporizador de alta resolución system_time que devuelve el número de microsegundos han transcurrido desde que el equipo se inicia.

uso

Ejemplo:

#include <kernel/OS.h> 

void time_measure_example() 
{ 
    bigtime_t prev_time_value, time_value; 
    bigtime_t time_diff; 

    /* Initial time */ 
    prev_time_value = system_time(); 

    /* Do some work here */ 

    /* Final time */ 
    time_value = system_time(); 

    /* Time difference */ 
    time_diff = time_value - prev_time_value; 
} 

OS/2

OS/2 tiene su propia API para recuperar alta precisión de fecha y hora:

  • consulta una frecuencia temporizador (ticks por unidad) con DosTmrQueryFreq (para el compilador de GCC):

    #define INCL_DOSPROFILE 
    #define INCL_DOSERRORS 
    #include <os2.h> 
    #include <stdint.h> 
    
    ULONG freq; 
    
    DosTmrQueryFreq (&freq); 
    
  • consulta el valor actual garrapatas con DosTmrQueryTime:

    escala
    QWORD tcounter; 
    unit64_t time_low; 
    unit64_t time_high; 
    unit64_t timestamp; 
    
    if (DosTmrQueryTime (&tcounter) == NO_ERROR) { 
        time_low = (unit64_t) tcounter.ulLo; 
        time_high = (unit64_t) tcounter.ulHi; 
    
        timestamp = (time_high << 32) | time_low; 
    } 
    
  • las garrapatas a tiempo transcurrido, es decir, a microsegundos:

    uint64_t usecs = (prev_timestamp - timestamp)/(freq/1000000); 
    

Ejemplo aplicación

Puede echar un vistazo a la biblioteca plibsys que implementa todas las estrategias descritas anteriormente (consulte ptimeprofiler * .c para obtener más información).

+0

"no existe una solución ANSI adecuada con suficiente precisión para el problema de medición de tiempo": hay C11 'timespec_get': https: // stackoverflow.com/a/36095407/895245 –

+0

Esta sigue siendo una forma incorrecta de medir el tiempo de ejecución del código. 'timespec_get' no es monotónico. –

+0

Gracias, tienes razón. –

Cuestiones relacionadas