objective c - Reloj monotónico en OSX
objective-c macos (3)
Después de buscar algunas respuestas diferentes para esto, terminé definiendo un encabezado que emula clock_gettime en mach:
#include <sys/types.h>
#include <sys/_types/_timespec.h>
#include <mach/mach.h>
#include <mach/clock.h>
#ifndef mach_time_h
#define mach_time_h
/* The opengroup spec isn''t clear on the mapping from REALTIME to CALENDAR
being appropriate or not.
http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html */
// XXX only supports a single timer
#define TIMER_ABSTIME -1
#define CLOCK_REALTIME CALENDAR_CLOCK
#define CLOCK_MONOTONIC SYSTEM_CLOCK
typedef int clockid_t;
/* the mach kernel uses struct mach_timespec, so struct timespec
is loaded from <sys/_types/_timespec.h> for compatability */
// struct timespec { time_t tv_sec; long tv_nsec; };
int clock_gettime(clockid_t clk_id, struct timespec *tp);
#endif
y en mach_gettime.c
#include "mach_gettime.h"
#include <mach/mach_time.h>
#define MT_NANO (+1.0E-9)
#define MT_GIGA UINT64_C(1000000000)
// TODO create a list of timers,
static double mt_timebase = 0.0;
static uint64_t mt_timestart = 0;
// TODO be more careful in a multithreaded environement
int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
kern_return_t retval = KERN_SUCCESS;
if( clk_id == TIMER_ABSTIME)
{
if (!mt_timestart) { // only one timer, initilized on the first call to the TIMER
mach_timebase_info_data_t tb = { 0 };
mach_timebase_info(&tb);
mt_timebase = tb.numer;
mt_timebase /= tb.denom;
mt_timestart = mach_absolute_time();
}
double diff = (mach_absolute_time() - mt_timestart) * mt_timebase;
tp->tv_sec = diff * MT_NANO;
tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA);
}
else // other clk_ids are mapped to the coresponding mach clock_service
{
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), clk_id, &cclock);
retval = clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
tp->tv_sec = mts.tv_sec;
tp->tv_nsec = mts.tv_nsec;
}
return retval;
}
CLOCK_MONOTONIC no parece disponible, por lo que clock_gettime está fuera.
He leído en algunos lugares que mach_absolute_time () podría ser el camino correcto, pero después de leer que era un ''valor dependiente de la CPU'', instantáneamente me pregunto si está usando rtdsc por debajo. Por lo tanto, el valor podría variar con el tiempo incluso si es monótono. Además, los problemas con la afinidad de subprocesos podrían dar como resultado resultados significativamente diferentes al llamar a la función (por lo que no es monotónico en todos los núcleos).
Por supuesto, eso es sólo especulación. ¿Alguien sabe cómo funciona realmente mach_absolute_time? De hecho, estoy buscando un reemplazo para clock_gettime (CLOCK_MONOTONIC ... o algo parecido para OSX. No importa cuál sea la fuente del reloj, espero al menos milisegundos de precisión y milisegundos de precisión.
Simplemente me gustaría entender qué relojes están disponibles, qué relojes son monótonos, si ciertos relojes se desvían, tienen problemas de afinidad de subprocesos, no son compatibles con todo el hardware de Mac o toman un número "súper alto" de ciclos de CPU para ejecutar.
Aquí están los enlaces que pude encontrar sobre este tema (algunos ya son enlaces muertos y no se pueden encontrar en archive.org):
https://developer.apple.com/library/mac/#qa/qa1398/_index.html http://www.wand.net.nz/~smr26/wordpress/2009/01/19/monotonic-time-in -mac-os-x / http://www.meandmark.com/timing.pdf
¡Gracias! Brett
El kernel Mach proporciona acceso a los relojes del sistema, de los cuales la documentación anuncia al menos uno ( SYSTEM_CLOCK
) como incrementos monótonos.
#include <mach/clock.h>
#include <mach/mach.h>
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
mach_timespec_t
tiene nanosegundos de precisión. Aunque no estoy seguro de la exactitud.
Mac OS X soporta tres relojes:
-
SYSTEM_CLOCK
devuelve el tiempo desde el tiempo de arranque; -
CALENDAR_CLOCK
devuelve el tiempo UTC desde 1970-01-01; -
REALTIME_CLOCK
está en desuso y es el mismo queSYSTEM_CLOCK
en su implementación actual.
La documentación para clock_get_time
dice que los relojes se incrementan monotónicamente a menos que alguien llame clock_set_time
. Las llamadas a clock_set_time
se clock_set_time
, ya que podría romper la propiedad monotónica de los relojes y, de hecho, la implementación actual devuelve KERN_FAILURE
sin hacer nada.
Sólo tiene que utilizar Mach Time .
Es una API pública, funciona en macOS, iOS y tvOS y funciona desde dentro de la caja de arena.
Mach Time devuelve una unidad de tiempo abstracta que normalmente llamo " tics de reloj ". La duración de una marca de reloj es específica del sistema y depende de la CPU. En los sistemas Intel actuales, una marca de reloj es, de hecho, exactamente un nanosegundo, pero no puede confiar en eso (puede ser diferente para ARM y ciertamente fue diferente para las CPU PowerPC). El sistema también puede indicarle el factor de conversión para convertir tics de reloj en nanosegundos y nanosegundos en tictac de reloj (este factor es estático, nunca cambiará en tiempo de ejecución). Cuando su sistema arranca, el reloj comienza a 0
y luego aumenta monótonamente con cada tic del reloj a partir de entonces, por lo que también puede usar Mach Time para obtener el tiempo de actividad de su sistema (y, por supuesto, ¡el tiempo de actividad es monótono!).
Aquí hay un código:
#include <stdio.h>
#include <inttypes.h>
#include <mach/mach_time.h>
int main ( ) {
uint64_t clockTicksSinceSystemBoot = mach_absolute_time();
printf("Clock ticks since system boot: %"PRIu64"/n",
clockTicksSinceSystemBoot
);
static mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
// Cast to double is required to make this a floating point devision,
// otherwise it would be an interger division and only the result would
// be converted to floating point!
double clockTicksToNanosecons = (double)timebase.numer / timebase.denom;
uint64_t systemUptimeNanoseconds = (uint64_t)(
clockTicksToNanosecons * clockTicksSinceSystemBoot
);
uint64_t systemUptimeSeconds = systemUptimeNanoseconds / (1000 * 1000 * 1000);
printf("System uptime: %"PRIu64" seconds/n", systemUptimeSeconds);
}
También puede poner un hilo para dormir hasta que se haya alcanzado un cierto Mach Time. Aquí hay un código para eso:
// Sleep for 750 ns
uint64_t machTimeNow = mach_absolute_time();
uint64_t clockTicksToSleep = (uint64_t)(750 / clockTicksToNanosecons);
uint64_t machTimeIn750ns = machTimeNow + clockTicksToSleep;
mach_wait_until(machTimeIn750ns);
Como Mach Time no tiene relación con ningún tiempo de reloj de pared, puedes jugar con la configuración de fecha y hora del sistema que desees, eso no tendrá ningún efecto en Mach Time.
Sin embargo, hay una consideración especial que puede hacer que Mach Time no sea adecuado para ciertos casos de uso: ¡El reloj de la CPU no se está ejecutando mientras el sistema está inactivo! Entonces, si hace que un hilo espere 5 minutos y después de 1 minuto el sistema se pone en reposo y se queda dormido durante 30 minutos, el hilo seguirá esperando otros 4 minutos después de que el sistema se haya activado, ya que los 30 minutos de tiempo de sueño no cuentan. ! El reloj de la CPU estaba descansando también durante ese tiempo. Sin embargo, en otros casos, esto es exactamente lo que quieres que suceda.
Mach Time también es una forma muy precisa de medir el tiempo empleado. Aquí hay un código que muestra esa tarea:
// Measure time
uint64_t machTimeBegin = mach_absolute_time();
sleep(1);
uint64_t machTimeEnd = mach_absolute_time();
uint64_t machTimePassed = machTimeEnd - machTimeBegin;
uint64_t timePassedNS = (uint64_t)(
machTimePassed * clockTicksToNanosecons
);
printf("Thread slept for: %"PRIu64" ns/n", timePassedNS);
Verá que el subproceso no duerme exactamente por un segundo, eso se debe a que lleva un subproceso a dormir, a volver a activarlo e incluso cuando está activo, no obtendrá tiempo de CPU inmediatamente si todos los núcleos Ya estamos ocupados ejecutando un hilo en ese momento.
Actualización (2018-09-26)
Desde macOS 10.12 (Sierra) también existe mach_continuous_time
. La única diferencia entre mach_continuous_time
y mach_absolute_time
es que el tiempo continuo también avanza cuando el sistema está en reposo. Por lo tanto, en el caso de que esto haya sido un problema hasta el momento y una razón para no usar Mach Time, la versión 10.12 y superior ofrecen una solución a este problema. El uso es exactamente el mismo que el descrito anteriormente.
También comenzando con macOS 10.9 (Mavericks), hay un mach_approximate_time
y en 10.12 también hay un mach_continuous_approximate_time
. Estos dos son idénticos a mach_absolute_time
y mach_continuous_time
con la única diferencia, que son más rápidos pero menos precisos. Las funciones estándar requieren una llamada al kernel ya que el kernel se encarga de Mach Time. Tal llamada es algo costosa, especialmente en sistemas que ya tienen una solución de Meltdown . Las versiones aproximadas no tendrán que llamar siempre al kernel. Utilizan un reloj en el espacio de usuario que solo se sincroniza con el reloj del kernel de vez en cuando para evitar que se desincronice demasiado, aunque siempre es posible una pequeña desviación y, por lo tanto, es solo el Tiempo de Mach "aproximado".