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.
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.
Respuestas:
No hay una función ANSI C que proporcione una resolución mejor que 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 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.000870
a mi máquina.
timeval::tv_usec
siempre 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);
timersub
función. Podemos usar tval_result
valores (tv_sec y tv_usec) tal cual.
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
CLOCKS_PER_SEC / 1000
podría ser inexacta, lo que podría afectar el resultado final (aunque en mi experiencia CLOCKS_PER_SEC
siempre ha sido un múltiplo de 1000). Hacer (1000 * clock()) / CLOCKS_PER_SEC
es menos susceptible a la inexactitud de división, pero por otro lado es más susceptible al desbordamiento. Solo algunos asuntos a considerar.
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);
}
clock_gettime(CLOCK_MONOTONIC, ...)
e incluso existe la macro de prueba de características _POSIX_MONOTONIC_CLOCK
.
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:
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
, 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 1000
para 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_gettime
llamada 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_gettime
soporte CLOCK_MONOTONIC
, por lo que lo primero que debe hacer es verificar su disponibilidad:
_POSIX_MONOTONIC_CLOCK
se define a un valor >= 0
significa que CLOCK_MONOTONIC
está disponible;si _POSIX_MONOTONIC_CLOCK
está definido 0
significa 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
El uso de clock_gettime
es 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 gettimeofday
llamada: 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_gettime
llamada, pero le falta CLOCK_MONOTONIC
. En cambio, tiene su propia fuente de reloj monotónico definida como la 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 actual del temporizador en nanosegundos. Aunque las versiones más nuevas de Solaris pueden tener clock_gettime
, puede seguir 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 clock_gettime
, pero admite gethrtime
lo 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 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).
timespec_get
: stackoverflow.com/a/36095407/895245
timespec_get
No es monótono.
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 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 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_UTC
actualmente 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_gettime
llamada al sistema.
Tenga en cuenta que este no es un método de microevaluación a prueba de fallas porque:
man clock_gettime
dice 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?
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.
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_sec
es la parte del segundo, y el tv_usec
es 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
1522139692342
exactamente un segundo