timeval library example c optimization clock gettimeofday

library - struct timeval linux



equivalente más rápido de gettimeofday (5)

Al tratar de crear una aplicación muy sensible a la latencia, que necesita enviar cientos de mensajes por segundo, cada mensaje tiene el campo de tiempo, queríamos considerar la optimización de gettimeofday. Primero pensamos en la optimización basada en rdtsc . Alguna idea ? Cualquier otro punteros? La precisión requerida del valor de tiempo devuelto es en milisegundos, pero no es un gran problema si el valor está ocasionalmente fuera de sincronización con el receptor durante 1-2 milisegundos. Tratar de hacerlo mejor que los 62 nanosegundos que toma el tiempo del día


Relojes POSIX

Escribí un punto de referencia para las fuentes de reloj POSIX:

  • tiempo (s) => 3 ciclos
  • ftime (ms) => 54 ciclos
  • gettimeofday (us) => 42 ciclos
  • clock_gettime (ns) => 9 ciclos (CLOCK_MONOTONIC_COARSE)
  • clock_gettime (ns) => 9 ciclos (CLOCK_REALTIME_COARSE)
  • clock_gettime (ns) => 42 ciclos (CLOCK_MONOTONIC)
  • clock_gettime (ns) => 42 ciclos (CLOCK_REALTIME)
  • clock_gettime (ns) => 173 ciclos (CLOCK_MONOTONIC_RAW)
  • clock_gettime (ns) => 179 ciclos (CLOCK_BOOTTIME)
  • clock_gettime (ns) => 349 ciclos (CLOCK_THREAD_CPUTIME_ID)
  • clock_gettime (ns) => 370 ciclos (CLOCK_PROCESS_CPUTIME_ID)
  • rdtsc (ciclos) => 24 ciclos

Estos números provienen de una CPU Intel Core i7-4771 a 3.50 GHz en Linux 4.0. Estas mediciones se tomaron usando el registro TSC y ejecutando cada método de reloj miles de veces y tomando el valor de costo mínimo.

Sin embargo, querrá probar en las máquinas en las que pretende ejecutar, ya que la forma en que se implementan varía según el hardware y la versión del kernel. El código se puede encontrar here . Se basa en el registro TSC para conteo cíclico, que está en el mismo repositorio ( tsc.h ).

TSC

Acceder al TSC (contador de marca de tiempo del procesador) es la forma más precisa y económica de medir el tiempo. En general, esto es lo que el kernel está usando. También es bastante directo en los modernos chips Intel ya que el TSC se sincroniza entre los núcleos y no se ve afectado por la escala de frecuencia. Por lo tanto, proporciona una fuente de tiempo global simple. Puede ver un ejemplo de cómo usarlo tsc.h con un recorrido del código de ensamblaje here .

El principal problema con esto (aparte de la portabilidad) es que no parece haber una buena forma de pasar de ciclos a nanosegundos. Los documentos de Intel, hasta donde puedo encontrar, indican que el TSC se ejecuta a una frecuencia fija, pero que esta frecuencia puede diferir de la frecuencia establecida por el procesador. Intel no parece proporcionar una forma confiable de determinar la frecuencia de TSC. El kernel de Linux parece resolver esto probando cuántos ciclos de TSC ocurren entre dos temporizadores de hardware (ver here ).

Memcached

Memcached se molesta en hacer el método de caché. Simplemente puede ser para asegurarse de que el rendimiento sea más predecible en todas las plataformas, o que escale mejor con múltiples núcleos. También puede no ser una optimización que valga la pena.


¿Has comparado realmente, y has encontrado que gettimeofday es inaceptablemente lento?

Con una velocidad de 100 mensajes por segundo, tiene 10 ms de tiempo de CPU por mensaje. Si tiene múltiples núcleos, suponiendo que se puede paralelizar por completo, puede aumentar fácilmente eso en 4-6x, ¡eso es 40-60ms por mensaje! No es probable que el costo de gettimeof sea cercano a los 10 ms. Sospecho que será más o menos de 1 a 10 microsegundos (en mi sistema, microbenchmarking da aproximadamente 1 microsegundo por llamada, pruébelo usted mismo ). Sus esfuerzos de optimización se gastarían mejor en otro lugar.

Si bien usar el TSC es una idea razonable, el Linux moderno ya tiene un espacio de usuario basado en TSC gettimeofday ; cuando sea posible, el vdso desplegará una implementación de gettimeofday que aplica un offset (leído desde un segmento de memoria kernel-user compartido) a rdtsc '' s valor, por lo tanto, calcula la hora del día sin ingresar el kernel. Sin embargo, algunos modelos de CPU no tienen un TSC sincronizado entre diferentes núcleos o diferentes paquetes, por lo que puede terminar siendo deshabilitado. Si desea una sincronización de alto rendimiento, primero debería considerar encontrar un modelo de CPU que tenga un TSC sincronizado.

Dicho eso, si está dispuesto a sacrificar una cantidad significativa de resolución (su tiempo solo será preciso hasta el último tic, lo que significa que podría estar desactivado en decenas de milisegundos), podría usar CLOCK_MONOTONIC_COARSE o CLOCK_REALTIME_COARSE con clock_gettime . Esto también se implementa con vdso y se garantiza que no se llamará al kernel (para kernels recientes y glibc).


¿Necesitas la precisión en milisegundos? Si no, simplemente puedes usar time() y lidiar con la marca de tiempo de Unix.


A continuación se muestra un punto de referencia. Veo unos 30ns. printTime () de rashad ¿Cómo obtener la hora y fecha actual en C ++?

#include <string> #include <iostream> #include <sys/time.h> using namespace std; void printTime(time_t now) { struct tm tstruct; char buf[80]; tstruct = *localtime(&now); strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct); cout << buf << endl; } int main() { timeval tv; time_t tm; gettimeofday(&tv,NULL); printTime((time_t)tv.tv_sec); for(int i=0; i<100000000; i++) gettimeofday(&tv,NULL); gettimeofday(&tv,NULL); printTime((time_t)tv.tv_sec); printTime(time(NULL)); for(int i=0; i<100000000; i++) tm=time(NULL); printTime(time(NULL)); return 0; }

3 segundos para 100,000,000 llamadas o 30ns;

2014-03-20.09:23:35 2014-03-20.09:23:38 2014-03-20.09:23:38 2014-03-20.09:23:41


Como dice Bdonian, si solo estás enviando unos cientos de mensajes por segundo, obtener el tiempo del día será lo suficientemente rápido.

Sin embargo, si estuvieras enviando millones de mensajes por segundo, podría ser diferente (pero aún así debes medir que es un cuello de botella). En ese caso, es posible que desee considerar algo como esto:

  • tener una variable global, dando la marca de tiempo actual en la precisión deseada
  • tener un hilo de fondo dedicado que no hace nada excepto actualizar la marca de tiempo (si la marca de tiempo debe actualizarse cada T unidades de tiempo, hacer que el hilo duerma una fracción de T y luego actualizar la marca de tiempo; usar funciones en tiempo real si es necesario)
  • todos los otros hilos (o el proceso principal, si no usas hilos de otro modo) simplemente lee la variable global

El lenguaje C no garantiza que pueda leer el valor de la marca de tiempo si es mayor que sig_atomic_t . Podría usar el bloqueo para manejar eso, pero el bloqueo es pesado. En su lugar, podría usar una variable volatile sig_atomic_t para indexar una matriz de marcas de tiempo: la cadena de fondo actualiza el siguiente elemento en la matriz y luego actualiza el índice. Los otros subprocesos leen el índice y luego leen el conjunto: es posible que obtengan un sello de tiempo obsoleto (pero obtienen el correcto la próxima vez), pero no se topan con el problema donde leen la fecha y hora en al mismo tiempo que se actualiza, y obtener algunos bytes del valor anterior y parte del nuevo valor.

Pero todo esto es demasiado exagerado para solo cientos de mensajes por segundo.