equivalente más rápido de gettimeofday

8 minutos de lectura

avatar de usuario
Depurador humilde

Al tratar de crear una aplicación muy sensible a la latencia, que necesita enviar cientos de mensajes por segundo, cada mensaje con el campo de tiempo, queríamos considerar optimizar gettimeofday. Nuestro primer pensamiento fue rdtsc optimización basada Alguna idea ? ¿Algún otro consejo? La precisión requerida del valor de tiempo devuelto es en milisegundos, pero no es gran cosa si el valor ocasionalmente no está sincronizado con el receptor durante 1 o 2 milisegundos. Intentar hacerlo mejor que los 62 nanosegundos que toma gettimeofday

  • ¿Con qué granularidad también debe ser precisa la hora?

    –David Aguas

    27 de junio de 2011 a las 21:10

  • Tenga en cuenta que es posible que el contador de marca de tiempo no se sincronice entre las CPU, según el modelo de CPU. Además, el Linux moderno implementará gettimeofday en el espacio de usuario con rdtsc donde sea posible

    – bdonlan

    27 de junio de 2011 a las 21:14

  • Está seguro gettimeofday() ¿es un problema? ¿Qué sistema operativo estás usando? En Linux, IIRC, se movió al espacio de usuario (a la página de vsyscall, o vDSO, no recuerdo cuál) para permitirle escalar a muchas CPU (lo hizo Christoph Lameter, IIRC de SGI).

    – ninjalj

    27 de junio de 2011 a las 21:18

  • vsyscall tenía un gettimeofday, pero vsyscall quedó obsoleto y su gettimeofday ahora es solo un código auxiliar que llama al kernel.

    – bdonlan

    27 de junio de 2011 a las 21:20

  • @bdonlan, ¿hay alguna forma de asegurarse de esto?

    – Depurador humilde

    29 de junio de 2011 a las 12:34


avatar de usuario
david terei

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 son de una CPU Intel Core i7-4771 a 3,50 GHz en Linux 4.0. Estas medidas se tomaron utilizando 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 la versión del hardware y del kernel. El código se puede encontrar aquí. Se basa en el registro TSC para el conteo de ciclos, 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 cronometrar las cosas. Generalmente, esto es lo que el kernel está usando. También es bastante sencillo en los chips Intel modernos, ya que el TSC está sincronizado entre núcleos y no se ve afectado por la escala de frecuencia. Por lo tanto, proporciona una fuente de tiempo global simple. Puedes ver un ejemplo de uso aquí con un tutorial del código ensamblador aquí.

El problema principal con esto (aparte de la portabilidad) es que no parece haber una buena manera 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 indicada por los procesadores. Intel no parece proporcionar una forma confiable de averiguar la frecuencia de TSC. El kernel de Linux parece resolver esto probando cuántos ciclos TSC ocurren entre dos temporizadores de hardware (ver aquí).

Memcaché

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

  • En su enlace de github tiene los mismos resultados, pero en nanosegundos, a diferencia de lo que escribe aquí por el factor 1000.

    – nh2

    3 de mayo de 2013 a las 4:38

  • lo siento, notación de tiempo fijo.

    – David Terei

    21 de junio de 2013 a las 22:32

  • ¿Cómo se puede siquiera comparar con una precisión de nanosegundos? ¿Hay alguna manera de garantizar que su programa sea el único que se ejecute y que no se permitan cambios de contexto?

    – Lundin

    3 de febrero de 2014 a las 12:45

  • @Lundin ejecutas un LOTE de rondas – suficiente donde el contexto cambia el factor.

    – haneefmubarak

    8 de junio de 2015 a las 3:43

  • @Peter – clock_gettime(CLOCK_MONOTONIC_COARSE) también es “más rápido que rdtsc” y también lee desde una ubicación de memoria en el VDSO. Sin embargo, hace un poco más de matemáticas, por lo que termina siendo un poco más costoso que el tiempo (), pero a veces es mucho más útil ya que tiene una resolución más alta. Es una pena que no sea aún más rápido, aunque siempre puede “hacer el suyo” con una señal periódica (o hilo que duerme) que actualiza una ubicación de memoria compartida; entonces realmente puede tener sus lecturas de 1 uop de un máximo ( ish) reloj de resolución.

    – BeeOnRope

    19 de agosto de 2018 a las 14:35

avatar de usuario
bdonlan

¿Realmente has comparado y encontrado gettimeofday ser inaceptablemente lento?

A una tasa de 100 mensajes por segundo, tiene 10 ms de tiempo de CPU por mensaje. Si tiene varios núcleos, suponiendo que se pueda paralelizar por completo, puede aumentarlo fácilmente de 4 a 6 veces, ¡eso es de 40 a 60 ms por mensaje! Es poco probable que el costo de gettimeofday se acerque a los 10 ms; sospecho que es más como 1 a 10 microsegundos (en mi sistema, el microbenchmarking da aproximadamente 1 microsegundo por llamada) pruébalo por ti mismo). Sus esfuerzos de optimización estarían mejor gastados en otra parte.

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 extraerá una implementación de gettimeofday que aplica un desplazamiento (leer de un segmento de memoria de usuario de kernel compartido) para rdtscvalor de , calculando así la hora del día sin entrar en el kernel. Sin embargo, algunos modelos de CPU no tienen un TSC sincronizado entre diferentes núcleos o diferentes paquetes, por lo que esto puede terminar deshabilitado. Si desea una temporización de alto rendimiento, primero puede considerar encontrar un modelo de CPU que tenga un TSC sincronizado.

Dicho esto, si está dispuesto a sacrificar una cantidad significativa de resolución (su sincronización solo será precisa hasta el último tic, lo que significa que podría estar desviado por 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 llamará al kernel (para kernels recientes y glibc).

  • Cada proceso es de un solo subproceso. El servidor normalmente tendrá de 10 a 20 de estos procesos en ejecución.

    – Depurador humilde

    29 de junio de 2011 a las 12:38

  • “Modelo de CPU que tiene un TSC sincronizado”, tiene un Xeon 5680, investigará sobre su manejo de esto

    – Depurador humilde

    29 de junio de 2011 a las 12:41

  • @Humble, busque “Marcar TSC inestable” en su dmesg. Si está allí, no está usando TSC. Pero siempre, siempre compare antes de intentar optimizar. No solo no sabes si es lo suficientemente rápido para empezar, sino que si no comparas, nunca sabrás si haces una mejora…

    – bdonlan

    29 de junio de 2011 a las 15:23


  • @bdonlan dmesg | grep TSC dice Fast TSC calibration using PIT

    – Depurador humilde

    29 de junio de 2011 a las 15:38

  • Obteniendo alrededor de 178 ciclos para gettimeofday()por lo que alrededor de 0,06 microsegundos por llamada.

    – Depurador humilde

    29 de junio de 2011 a las 16:31

Como dice bdonian, si solo estás enviando unos pocos cientos de mensajes por segundo, gettimeofday va a ser lo suficientemente rápido.

Sin embargo, si estuviera enviando millones de mensajes por segundo, podría ser diferente (pero aún debería la medida 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 con la precisión deseada
  • tenga un hilo de fondo dedicado que no haga nada excepto actualizar la marca de tiempo (si la marca de tiempo debe actualizarse cada T unidades de tiempo, entonces haga que el hilo duerma una fracción de T y luego actualice la marca de tiempo; use funciones en tiempo real si es necesario)
  • todos los demás subprocesos (o el proceso principal, si no usa subprocesos de otra manera) solo 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 lidiar con eso, pero el bloqueo es pesado. En su lugar, podrías usar un volatile sig_atomic_t variable escrita para indexar una matriz de marcas de tiempo: el subproceso de fondo actualiza el siguiente elemento de la matriz y luego actualiza el índice. Los otros subprocesos leen el índice y luego leen la matriz: es posible que obtengan una marca de tiempo un poco desactualizada (pero obtienen la correcta la próxima vez), pero no se encuentran con el problema de leer la marca de tiempo en al mismo tiempo que se actualiza, y obtenga algunos bytes del valor anterior y algunos del valor nuevo.

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

  • “tener un subproceso 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” <-- esto es exactamente lo que hace CLOCK_*_COARSE, excepto que el subproceso dedicado es en realidad un controlador de interrupciones y es sistema- de ancho, y la gente del núcleo ya se ha ocupado del desgarro de lectura y otros problemas para usted :)

    – bdonlan

    27 de junio de 2011 a las 21:47


  • No estoy seguro de que sea más rápido que el de Linux. gettimeofday(): cada escritura podría causar una pérdida de caché en cada lector en SMP.

    – ninjalj

    27 de junio de 2011 a las 21:50

  • Ahora que lo pienso, ¿las vvars son cpu locales en Linux? Si es así, esa es otra gran ventaja de CLOCK_*_COARSE… Editar: Parece que no (lxr.linux.no/linux+v2.6.39/arch/x86/kernel/vsyscall_64.c#L76), pero invalidar una o dos líneas de caché es mejor que interrumpir todas las CPU con una interrupción de temporizador local o IPI, supongo

    – bdonlan

    27 de junio de 2011 a las 21:50


  • Lars, no se trata de cuántas veces por segundo, la aplicación quiere construir un mensaje y enviarlo lo antes posible al receptor, y está compitiendo con otros remitentes. Esta es una aplicación comercial, por lo que en cada mensaje al receptor, sin importar qué tan alta o baja sea la frecuencia, nos gustaría eliminar microsegundos.

    – Depurador humilde

    29 de junio de 2011 a las 12:37

  • Gracias por tu respuesta. Le daré una oportunidad.

    – Depurador humilde

    29 de junio de 2011 a las 12:44

avatar de usuario
edW

A continuación se muestra un punto de referencia. Veo alrededor de 30ns. printTime() de rashad ¿Cómo obtener la hora y la fecha actuales 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 30 ns;

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

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

  • Comparación de time() y gettimeofday(), 60 nanosegundos frente a 62 nanosegundos. No mucho, hay que hacerlo mucho mejor.

    – Depurador humilde

    29 de junio de 2011 a las 18:43

  • Tal vez tener un hilo con: global_unix_ts = time(); sleep 500ms;. La var global ni siquiera está protegida por un mutex. Esto debería encenderse rápido. Las respuestas de bdonlan también parecen ser muy elegantes y completas.

    – Vinicius Kamakura

    29 de junio de 2011 a las 18:47

  • Comparación de time() y gettimeofday(), 60 nanosegundos frente a 62 nanosegundos. No mucho, hay que hacerlo mucho mejor.

    – Depurador humilde

    29 de junio de 2011 a las 18:43

  • Tal vez tener un hilo con: global_unix_ts = time(); sleep 500ms;. La var global ni siquiera está protegida por un mutex. Esto debería encenderse rápido. Las respuestas de bdonlan también parecen ser muy elegantes y completas.

    – Vinicius Kamakura

    29 de junio de 2011 a las 18:47

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad