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


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

Author: zengr, 2008-12-12

8 answers

No hay ninguna función ANSI C que proporcione una resolución de tiempo mejor que 1 segundo, pero la función POSIX gettimeofday proporciona 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 de la siguiente manera:

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.

 81
Author: Robert Gamble,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-17 19:01:10
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
 43
Author: Nick Van Brunt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-28 19:18:30

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);
}
 25
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-19 09:09:42

Implementación de una solución portátil

Como ya se mencionó aquí que no hay 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 portátil y, si es posible, una solución de medición de tiempo de alta resolución.

Reloj monótono vs. marcas de tiempo

En términos generales, hay dos maneras de medir el tiempo:{[47]]}

  • reloj monótono; {[58]]}
  • actual (fecha)marca de tiempo.

El primero utiliza un contador de reloj monótono (a veces se llama contador de ticks) que cuenta ticks con una frecuencia predefinida, por lo que si tiene un valor de ticks y la frecuencia es conocida, puede convertir fácilmente ticks 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 ticks desde el inicio del sistema. Pero garantiza que un reloj siempre se ejecuta de una manera creciente independientemente del estado del sistema. Por lo general, la frecuencia está vinculada a una fuente de hardware de alta resolución, 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 (fecha)hora 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 tiempo puede verse afectado por diferentes ajustes de tiempo del sistema, es decir, tiempo cambio de zona, cambio de horario de verano (DST), actualización del servidor NTP, hibernación del sistema, etc. 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.

Así que la primera regla en la medición de intervalos de tiempo es usar un reloj monótono si es posible. Por lo general, tiene una alta precisión y es confiable por diseño.

Estrategia de reserva

Cuando implementación de una solución portátil vale la pena considerar una estrategia de reserva: use un reloj monotónico si está disponible y el enfoque de reserva a marcas de tiempo si no hay un reloj monotónico en el sistema.

Windows

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

  • Consulta una frecuencia de 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 solo necesita obtenerla una vez.

  • Consulta el valor actual de ticks con QueryPerformanceCounter :

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

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

De acuerdo con Microsoft, no debe tener cualquier problema con este enfoque en Windows XP y versiones posteriores en la mayoría de los casos. Pero también puede utilizar dos soluciones de reserva en Windows:

  • GetTickCount proporciona el número 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 de GetTickCount, pero está disponible a partir de Windows Vista y superiores.

OS X (macOS)

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

  • Obtener el numerador de frecuencia de reloj y denominador:

    #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.

  • Consulta el valor de tick actual con mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
    
  • Escala los ticks al tiempo transcurrido, es decir, a microsegundos, usando numerador y 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 es en nanosegundos, la dividimos por 1000 para obtener microsegundos. Puede encontrar el mismo enfoque utilizado en el time_mac de Chromium.c . Si realmente necesita una precisión de nanosegundos considere leer el ¿Cómo puedo usar mach_absolute_time sin desbordarse?.

Linux y UNIX

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

  • si _POSIX_MONOTONIC_CLOCK se define como un valor >= 0 significa que CLOCK_MONOTONIC está disponible;
  • Si _POSIX_MONOTONIC_CLOCK está definido como 0, significa que debe verificar adicionalmente si funciona en tiempo de ejecución, le sugiero que use 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 de reserva (consulte a continuación).

El uso de clock_gettime es bastante sencillo:

  • Obtenga 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 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 de reserva es usar la llamada gettimeofday : no es un 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 debes:

#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 de tiempo se reduce a microsegundos.

SGI IRIX

IRIX tiene la llamada clock_gettime, pero carece de CLOCK_MONOTONIC. En su lugar, tiene su propia fuente de reloj monótona 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 de temporizador de alta resolución gethrtime que devuelve el valor del temporizador actual en nanosegundos. Aunque las versiones más recientes de Solaris pueden tener clock_gettime, puede atenerse a gethrtime si 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 de clock_gettime, pero soporta gethrtime 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_time que devuelve el número de microsegundos transcurridos desde que se arrancó el equipo.

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:

  • Consulta 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);
    
  • Consulta 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;
    }
    
  • Escala las marcas al tiempo transcurrido, es decir, a microsegundos:

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

Ejemplo de implementación

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

 13
Author: Alexander Saprykin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:11

La mejor precisión que puede obtener es a través del uso de la instrucción "rdtsc" solo para x86, que puede proporcionar una resolución a nivel de reloj (por supuesto, ne debe tener en cuenta el costo de la llamada rdtsc, que se puede medir fácilmente al iniciar la aplicación).

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

 4
Author: Dark Shikari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-12 00:15:16

timespec_get de C11 devuelve hasta nanosegundos, redondeado a la resolución de la implementación.

Parece una estafa ANSI de POSIX' clock_gettime.

Ejemplo: un printf se hace 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 la base es TIME_UTC, el miembro tv_sec se establece en el número de segundos época definida por la implementación, truncada a todo el valor y el miembro tv_nsec es establece el número integral de nanosegundos, redondeado a la resolución del reloj del sistema. (321)

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

C++11 también obtuvo std::chrono::high_resolution_clock: C++ Cronómetro Multiplataforma de Alta Resolución

Glibc 2.21 implementa bajo sysdeps/posix/timespec_get.c as:

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:

  • Solo TIME_UTC está soportado actualmente

  • Se 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 llamada al sistema clock_gettime.

    Tenga en cuenta que este no es un método de micro-benchmarking a prueba de fallos porque:

    • man clock_gettime dice que esta medida puede tener discontinuidades si cambia algún tiempo del sistema configuración mientras se ejecuta el programa. Este 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 programador decide olvidarse de su tarea, parecerá que se ejecuta por más tiempo.

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

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

 4
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:27

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

Alse use gettimeofday, el tv_sec es la parte de 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;
 }

It print:

1522139691342 1522139692342, exactamente un segundo.

 1
Author: Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-27 08:40:24

Bajo windows:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
 -3
Author: Jichao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-11 06:11:18