linux - clock_realtime - clock_gettime en c
¿Es clock_gettime() adecuado para la temporización de submicrosegundos? (6)
Necesito un temporizador de alta resolución para el generador de perfiles integrado en la compilación de Linux de nuestra aplicación. Nuestro generador de perfiles mide ámbitos tan pequeños como funciones individuales, por lo que necesita una precisión de temporizador superior a 25 nanosegundos.
Anteriormente, nuestra implementación utilizaba el ensamblaje en línea y la operación rdtsc para consultar directamente el temporizador de alta frecuencia de la CPU, pero esto es problemático y requiere una recalibración frecuente.
Así que intenté usar la función clock_gettime
lugar de consultar CLOCK_PROCESS_CPUTIME_ID. Los documentos alegan que esto me da un tiempo de nanosegundos, pero descubrí que la sobrecarga de una sola llamada a clock_gettime()
era más de 250 ns. Eso hace que sea imposible cronometrar los eventos con una duración de 100 ns, y tener una sobrecarga tan elevada en la función del temporizador reduce considerablemente el rendimiento de la aplicación, distorsionando los perfiles más allá del valor. (Tenemos cientos de miles de nodos de perfilado por segundo).
¿Hay alguna manera de llamar a clock_gettime()
que tenga menos de ¼μs de sobrecarga? ¿O hay alguna otra forma en la que pueda obtener de manera confiable el contador de la marca de tiempo con una sobrecarga de <25ns? ¿O estoy atascado con el uso de rdtsc
?
A continuación se muestra el código que utilicé para clock_gettime()
.
// calls gettimeofday() to return wall-clock time in seconds:
extern double Get_FloatTime();
enum { TESTRUNS = 1024*1024*4 };
// time the high-frequency timer against the wall clock
{
double fa = Get_FloatTime();
timespec spec;
clock_getres( CLOCK_PROCESS_CPUTIME_ID, &spec );
printf("CLOCK_PROCESS_CPUTIME_ID resolution: %ld sec %ld nano/n",
spec.tv_sec, spec.tv_nsec );
for ( int i = 0 ; i < TESTRUNS ; ++ i )
{
clock_gettime( CLOCK_PROCESS_CPUTIME_ID, &spec );
}
double fb = Get_FloatTime();
printf( "clock_gettime %d iterations : %.6f msec %.3f microsec / call/n",
TESTRUNS, ( fb - fa ) * 1000.0, (( fb - fa ) * 1000000.0) / TESTRUNS );
}
// and so on for CLOCK_MONOTONIC, CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID.
Resultados:
CLOCK_PROCESS_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 3115.784947 msec 0.371 microsec / call
CLOCK_MONOTONIC resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2505.122119 msec 0.299 microsec / call
CLOCK_REALTIME resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2456.186031 msec 0.293 microsec / call
CLOCK_THREAD_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2956.633930 msec 0.352 microsec / call
Esto está en un kernel estándar de Ubuntu. La aplicación es un puerto de una aplicación de Windows (donde nuestro ensamblado en línea rdtsc funciona bien).
Apéndice:
¿El x86-64 GCC tiene algún equivalente intrínseco a __rdtsc() , por lo que al menos puedo evitar el ensamblaje en línea?
Necesito un temporizador de alta resolución para el generador de perfiles integrado en la compilación de Linux de nuestra aplicación. Nuestro generador de perfiles mide ámbitos tan pequeños como funciones individuales, por lo que necesita una precisión de temporizador superior a 25 nanosegundos.
¿Has considerado oprofile
o perf
? Puede usar el hardware del contador de rendimiento en su CPU para obtener datos de perfil sin agregar instrumentación al código en sí. Puede ver los datos por función, o incluso por línea de código. El "único" inconveniente es que no medirá el tiempo de reloj de pared consumido, medirá el tiempo de CPU consumido, por lo que no es apropiado para todas las investigaciones.
Ejecuté algunos puntos de referencia en mi sistema, que es un E5645 Xeon de cuatro núcleos que admite un kernel de ejecución de TSC constante 3.2.54 y los resultados fueron:
clock_gettime(CLOCK_MONOTONIC_RAW) 100ns/call
clock_gettime(CLOCK_MONOTONIC) 25ns/call
clock_gettime(CLOCK_REALTIME) 25ns/call
clock_gettime(CLOCK_PROCESS_CPUTIME_ID) 400ns/call
rdtsc (implementation @DavidSchwarz) 600ns/call
Así que parece que en un sistema razonablemente moderno, la respuesta (rdtsc) es la peor ruta para bajar.
Está llamando a clock_getttime con el parámetro de control, lo que significa que la API se está ramificando a través del árbol if-else para ver qué tipo de tiempo desea. Sé que no puedes evitar eso con esta llamada, pero mira si puedes profundizar en el código del sistema y llamar a lo que el kernal está llamando directamente. Además, observo que está incluyendo el tiempo de bucle (i ++ y la rama condicional).
No. Tendrás que usar un código específico de la plataforma para hacerlo. En x86 y x86-64, puede usar ''rdtsc'' para leer el rdtsc .
Solo tiene que trasladar el ensamblaje rdtsc que está utilizando.
__inline__ uint64_t rdtsc(void) {
uint32_t lo, hi;
__asm__ __volatile__ ( // serialize
"xorl %%eax,%%eax /n cpuid"
::: "%rax", "%rbx", "%rcx", "%rdx");
/* We cannot use "=A", since this would use %rax on x86_64 and return only the lower 32bits of the TSC */
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (uint64_t)hi << 32 | lo;
}
Prueba clockid_t CLOCK_MONOTONIC_RAW?
CLOCK_MONOTONIC_RAW (desde Linux 2.6.28; específico de Linux) Similar a CLOCK_MONOTONIC, pero proporciona acceso a un tiempo bruto basado en hardware que no está sujeto a los ajustes NTP o los ajustes incrementales realizados por adjtime (3).
De Man7.org
Sí, la mayoría de las plataformas modernas tendrán una llamada adecuada clock_gettime
que se implementará únicamente en el espacio de usuario utilizando el mecanismo VDSO, y tomará de forma confiable entre 20 y 30 nanosegundos.
Internamente, esto utiliza rdtsc
o rdtscp
para la parte detallada del rdtscp
, más los ajustes para mantener esto sincronizado con el tiempo de reloj de pared (dependiendo del reloj que elija) y una multiplicación para convertir de cualquier unidad que tenga rdtsc
En su plataforma a nanosegundos.
No todos los relojes ofrecidos por clock_gettime
implementarán este método rápido, y no siempre es obvious cuáles lo hacen. Por CLOCK_MONOTONIC
general, CLOCK_MONOTONIC
es una buena opción, pero debería probar esto en su propio sistema .