¿Cómo medir el tiempo en milisegundos usando ANSI C?

127

Usando solo ANSI C, ¿hay alguna forma de medir el tiempo con precisión de milisegundos o más? Estaba navegando time.h pero solo encontré segundas funciones de precisión.

corto
fuente
44
Tenga en cuenta la diferencia entre precisión y exactitud. Puede obtener un tiempo con precisión de milisegundos tomando el tiempo en segundos y multiplicándolo por 1000, pero no sirve de nada. Las funciones de precisión de ms no necesariamente tienen precisión de ms, aunque generalmente tienen una precisión mejor que 1s.
Steve Jessop
2
La respuesta simple es NO, ANSI C no admite precisión de milisegundos o mejor. La respuesta más compleja depende de lo que intente hacer: francamente, toda el área es una pesadilla, incluso si permite el uso de las funciones de Posix ampliamente disponibles. Utiliza el término "medida", así que supongo que está interesado en un intervalo en lugar del tiempo de "reloj de pared". Pero, ¿está intentando medir un período de tiempo absoluto o el uso de CPU por su proceso?
Varilla de nivel
2
Solo quería decirle a SOF que acaba de salvar mi tocino, nuevamente ;-)
corlettk

Respuestas:

92

No hay una función ANSI C que proporcione una resolución mejor que 1 segundo, pero la función POSIX gettimeofdayproporciona una resolución de microsegundos. La función de reloj solo mide la cantidad de tiempo que un proceso ha pasado ejecutándose y no es precisa en muchos sistemas.

Puede usar esta función así:

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 vuelve Time elapsed: 1.000870a mi máquina.

Robert Gamble
fuente
30
Ligera advertencia: gettimeofday () no es monótono, lo que significa que puede saltar (e incluso retroceder) si, por ejemplo, su máquina está tratando de mantener la sincronización con un servidor de tiempo de red u otra fuente de tiempo.
Varilla de nivel
3
Para ser precisos: En la norma ISO C99 (que creo que es compatible en esta parte de la norma ANSI C) no hay ni siquiera una garantía de cualquier resolución en el tiempo. (ISO C99, 7.23.1p4)
Roland Illig el
66
Vale la pena señalar que timeval::tv_usecsiempre es inferior a un segundo, está en bucle. Es decir, para tomar diferencias de tiempo mayores de 1 segundo, debe:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
Alexander Malakhov
44
@Dipstick: Pero tenga en cuenta que, por ejemplo, NTP nunca mueve su reloj hacia atrás hasta que se lo indique explícitamente.
thejh
1
La lógica de sustracción de tiempo de @AlexanderMalakhov se encapsula dentro de la timersubfunción. Podemos usar tval_resultvalores (tv_sec y tv_usec) tal cual.
x4444
48
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
Nick Van Brunt
fuente
CLOCKS_PER_SEC está establecido en 1000000 en muchos sistemas. Imprima su valor para estar seguro antes de usarlo de esta manera.
ysap
16
Dado que son relojes por segundo, no importa qué valor sea, el valor resultante de clock () / CLOCKS_PER_SEC estará en segundos (al menos debería ser). Dividir por 1000 convierte eso en milisegundos.
David Young
14
De acuerdo con el Manual de referencia C, los valores clock_t pueden ajustarse a partir de unos 36 minutos. Si está midiendo un cálculo largo, debe ser consciente de esto.
CyberSkull
44
También tenga en cuenta que la división entera CLOCKS_PER_SEC / 1000podría ser inexacta, lo que podría afectar el resultado final (aunque en mi experiencia CLOCKS_PER_SECsiempre ha sido un múltiplo de 1000). Hacer (1000 * clock()) / CLOCKS_PER_SECes menos susceptible a la inexactitud de división, pero por otro lado es más susceptible al desbordamiento. Solo algunos asuntos a considerar.
Cornstalks
44
¿No mide esto el tiempo de CPU y no el tiempo de pared?
krs013
28

Siempre uso la función clock_gettime (), devolviendo el tiempo del 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);
}

fuente
55
clock_gettime () no es ANSI C.
PowerApp101
1
Además, CLOCK_MONOTONIC no está implementado en muchos sistemas (incluidas muchas plataformas Linux).
Varilla de nivel
2
@ PowerApp101 No hay una forma buena / robusta de ANSI C de hacer esto. Muchas de las otras respuestas se basan en POSIX en lugar de ANCI C. Dicho esto, creo que hoy. @Dipstick Hoy, creo que la mayoría de las plataformas modernas [cita requerida] son ​​compatibles clock_gettime(CLOCK_MONOTONIC, ...)e incluso existe la macro de prueba de características _POSIX_MONOTONIC_CLOCK.
omninonsense
22

Implementando una solución portátil

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

Reloj monótono vs. sellos de tiempo

En términos generales, hay dos formas de medir el tiempo:

  • reloj monotónico;
  • marca de tiempo actual (fecha).

El primero usa un contador de reloj monótono (a veces se lo llama contador de ticks) que cuenta los ticks con una frecuencia predefinida, por lo que si tiene un valor de ticks y se conoce la frecuencia, puede convertirlos fácilmente en tiempo transcurrido. En realidad, no se garantiza que un reloj monótono refleje la hora actual del sistema de ninguna manera, también puede contar los tics desde el inicio del sistema. Pero garantiza que un reloj siempre se ejecute de manera creciente independientemente del estado del sistema. Por lo general, la frecuencia está vinculada 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 hora (fecha) basado en el valor actual del reloj del sistema. También puede tener una alta resolución, pero tiene un inconveniente importante: este tipo de valor de hora puede verse afectado por diferentes ajustes de hora del sistema, es decir, cambio de zona horaria, cambio de horario de verano (DST), actualización del servidor NTP, hibernación del sistema, etc. 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 la primera.

Entonces, 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 reserva

Al implementar una solución portátil, vale la pena considerar una estrategia alternativa: use un reloj monotónico si está disponible y un enfoque de retroceso a las marcas de tiempo si no hay un reloj monotónico en el sistema.

Ventanas

Hay un gran artículo llamado Adquisición de marcas de tiempo de alta resolución en MSDN sobre la medición de tiempo en Windows que describe todos los detalles que puede necesitar saber sobre el soporte de software y hardware. Para adquirir una marca de tiempo de alta precisión en Windows, debe:

  • consultar una frecuencia de temporizador (tics 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 debe obtenerla solo una vez.

  • consultar el valor actual de las marcas con QueryPerformanceCounter :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    if (QueryPerformanceCounter (&tcounter) != 0)
        tick_value = tcounter.QuadPart;
  • escale los ticks al tiempo transcurrido, es decir, a microsegundos:

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

Según Microsoft, no debería tener ningún problema con este enfoque en Windows XP y versiones posteriores en la mayoría de los casos. Pero también puede usar dos soluciones de respaldo en Windows:

  • GetTickCount proporciona la cantidad de milisegundos que han transcurrido desde que se inició el 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 GetTickCount, pero está disponible a partir de Windows Vista y superior.

OS X (macOS)

OS X (macOS) tiene sus propias unidades de tiempo absoluto de Mach que representan un reloj monótono. La mejor manera de comenzar es el artículo de Apple Preguntas y respuestas técnicas QA1398: Unidades de tiempo absoluto de Mach 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 alternativa clock_gettime en Mac OS X que al final puede dejarle un poco confundido sobre qué hacer con el posible desbordamiento del valor porque la frecuencia del contador se usa en forma de numerador y denominador. Entonces, un breve ejemplo de cómo obtener el tiempo transcurrido:

  • obtener el numerador y denominador de 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;
        }
    }

    Necesitas hacer eso solo una vez.

  • consultar el valor actual de la marca con mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
  • escale los ticks al tiempo transcurrido, es decir, a microsegundos, utilizando el numerador y el denominador previamente consultados:

    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 para evitar un desbordamiento es reducir las marcas a la precisión deseada antes de usar el numerador y el denominador. Como la resolución inicial del temporizador está en nanosegundos, la dividimos 1000para obtener microsegundos. Puede encontrar el mismo enfoque utilizado en time_mac.c de Chromium . Si realmente necesita una precisión de nanosegundos, considere leer el ¿Cómo puedo usar mach_absolute_time sin desbordar? .

Linux y UNIX

La clock_gettimellamada es su mejor manera en cualquier sistema compatible con POSIX. Puede consultar el tiempo desde diferentes fuentes de reloj, y el que necesitamos es CLOCK_MONOTONIC. No todos los sistemas tienen clock_gettimesoporte CLOCK_MONOTONIC, por lo que lo primero que debe hacer es verificar su disponibilidad:

  • si _POSIX_MONOTONIC_CLOCKse define a un valor >= 0significa que CLOCK_MONOTONICestá disponible;
  • si _POSIX_MONOTONIC_CLOCKestá definido 0significa que también debe verificar si funciona en tiempo de ejecución, sugiero usar sysconf:

    #include <unistd.h>
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
        /* A monotonic clock presents */
    }
    #endif
  • de lo contrario, no se admite un reloj monótono y debe usar una estrategia alternativa (ver más abajo)

El uso de clock_gettimees bastante sencillo:

  • obtener el valor del 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 a microsegundos aquí.

  • Calcule la diferencia con el valor de tiempo anterior recibido de 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 alternativa es utilizar la gettimeofdayllamada: no es monótono, pero proporciona una resolución bastante buena. 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;
}

Nuevamente, el valor del tiempo se reduce a microsegundos.

SGI IRIX

IRIX tiene la clock_gettimellamada, pero le falta CLOCK_MONOTONIC. En cambio, tiene su propia fuente de reloj monotónico definida como la CLOCK_SGI_CYCLEque debe usar en lugar de CLOCK_MONOTONICcon clock_gettime.

Solaris y HP-UX

Solaris tiene su propia interfaz de temporizador de alta resolución gethrtimeque devuelve el valor actual del temporizador en nanosegundos. Aunque las versiones más nuevas de Solaris pueden tener clock_gettime, puede seguir gethrtimesi necesita soportar versiones antiguas de Solaris.

El 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 admite gethrtimelo que debe usar de la misma manera que en Solaris.

BeOS

BeOS también tiene su propia interfaz de temporizador de alta resolución system_timeque devuelve el número de microsegundos transcurridos desde que se inició la computadora.

Ejemplo de uso:

#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 marcas de tiempo de alta precisión:

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

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

    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;
    }
  • escale los ticks al tiempo transcurrido, es decir, a microsegundos:

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

Implementación de ejemplo

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

Alexander Saprykin
fuente
"no existe una solución ANSI adecuada con suficiente precisión para el problema de medición de tiempo": hay C11 timespec_get: stackoverflow.com/a/36095407/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Esta sigue siendo una forma incorrecta de medir el tiempo de ejecución del código. timespec_getNo es monótono.
Alexander Saprykin el
11

timespec_get de C11

Devuelve hasta nanosegundos, redondeados a la resolución de la implementación.

Parece una estafa ANSI de POSIX ' clock_gettime.

Ejemplo: a printfse 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 borrador estándar C11 N1570 7.27.2.5 "La función timespec_get dice":

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

321) Aunque un objeto de estructura de tiempo describe tiempos con resolución de nanosegundos, la resolución disponible depende del sistema e incluso puede ser mayor de 1 segundo.

C ++ 11 también obtuvo std::chrono::high_resolution_clock: Temporizador de alta resolución multiplataforma C ++

implementación de glibc 2.21

Se puede encontrar en sysdeps/posix/timespec_get.c:

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

  • TIME_UTCactualmente solo es compatible

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

    Linux x86-64 tiene una clock_gettimellamada al sistema.

    Tenga en cuenta que este no es un método de microevaluación a prueba de fallas porque:

    • man clock_gettimedice que esta medida puede tener discontinuidades si cambia alguna configuración de hora del sistema mientras se ejecuta el programa. Esto debería ser un evento raro, por supuesto, y es posible que pueda ignorarlo.

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

    Por esas razones, getrusage()podría ser una mejor herramienta de evaluación comparativa POSIX, a pesar de su precisión máxima más baja en microsegundos.

    Más información en: Mida el tiempo en Linux: ¿tiempo vs reloj vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
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 chip (versiones más nuevas de procesadores x86 con extensiones PT y las versiones más nuevas correspondientes de Linux kernel / perf)
4

La mejor precisión que puede obtener es mediante el uso de la instrucción "rdtsc" solo x86, que puede proporcionar una resolución a nivel de reloj (por supuesto, no debe tener en cuenta el costo de la llamada rdtsc en sí, que se puede medir fácilmente en inicio de la aplicación).

El problema principal aquí es medir la cantidad de relojes por segundo, que no debería ser demasiado difícil.

Shikari oscuro
fuente
3
También es posible que deba preocuparse por la afinidad del procesador, porque en algunas máquinas puede enviar las llamadas RDTSC a más de un procesador y sus contadores RDTSC pueden no estar sincronizados.
Will Dean
1
Y además, algunos procesadores no tienen un TSC que aumente de forma monótona; piense en los modos de ahorro de energía que reducen la frecuencia de la CPU. Usar RDTSC para cualquier cosa que no sean tiempos localizados muy cortos es una MUY mala idea.
snemarch
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 (¿temprano?) CPU AMD64 de múltiples núcleos: tuve que limitarme a la afinidad de un solo núcleo en mi x2 4400+ para Una serie de títulos.
snemarch
2

La respuesta aceptada es lo suficientemente buena. Pero mi solución es más simple. Solo pruebo en Linux, uso gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.

Además gettimeofday, el tv_seces la parte del segundo, y el tv_useces microsegundos , no milisegundos .

long currentTimeMillis() {
  struct timeval time;
  gettimeofday(&time, NULL);

  return time.tv_sec * 1000 + time.tv_usec / 1000;
}

int main() {
  printf("%ld\n", currentTimeMillis());
  // wait 1 second
  sleep(1);
  printf("%ld\n", currentTimeMillis());
  return 0;
 }

Imprime:

1522139691342 1522139692342exactamente un segundo

Mirada lasciva
fuente
-4

Debajo de ventanas:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
Jichao
fuente
1
¿Es este Ansi C según lo solicitado?
Gyom