¿Se garantiza que gettimeofday () tiene una resolución de microsegundos?

97

Estoy portando un juego, que fue escrito originalmente para la API Win32, a Linux (bueno, portando el puerto OS X del puerto Win32 a Linux).

Lo he implementado QueryPerformanceCounterdando los uSeconds desde que se inició el proceso:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

Esto, junto con QueryPerformanceFrequency()dar una constante de 1000000 como frecuencia, funciona bien en mi máquina , dándome una variable de 64 bits que contiene uSecondsdesde el inicio del programa.

Entonces, ¿ es esto portátil? No quiero descubrir que funciona de manera diferente si el kernel se compiló de cierta manera o algo así. Sin embargo, estoy de acuerdo con que no sea portátil a algo que no sea Linux.

Bernardo
fuente

Respuestas:

57

Tal vez. Pero tienes problemas mayores. gettimeofday()puede resultar en tiempos incorrectos si hay procesos en su sistema que cambian el temporizador (es decir, ntpd). En un linux "normal", sin embargo, creo que la resolución de gettimeofday()es 10us. Puede saltar hacia adelante y hacia atrás y el tiempo, en consecuencia, en función de los procesos que se ejecutan en su sistema. Esto efectivamente hace que la respuesta a su pregunta no.

Debería buscar clock_gettime(CLOCK_MONOTONIC)intervalos de tiempo. Sufre de varios problemas menores debido a cosas como sistemas de múltiples núcleos y configuraciones de reloj externo.

Además, mire la clock_getres()función.

Louis Brandy
fuente
1
clock_gettime está presente solo en la versión más reciente de Linux. otro sistema solo tiene gettimeofday ()
vitaly.v.ch
3
@ vitaly.v.ch es POSIX, ¿así que no es solo Linux y 'newist'? incluso las distribuciones 'Enterprise' como Red Hat Enterprise Linux se basan en 2.6.18, que tiene clock_gettime, así que no, no es muy nuevo ... (la fecha de la página de manual en RHEL es 2004-March-12, por lo que existe desde hace un tiempo) hablando de kernels VIEJOS REALMENTE FREAKING, ¿qué quieres decir?
Spudd86
clock_gettime se incluyó en POSIX en 2001. Hasta donde yo sé, actualmente clock_gettime () implementado en Linux 2.6 y qnx. pero linux 2.4 se usa actualmente en muchos sistemas de producción.
vitaly.v.ch
Se introdujo en 2001, pero no es obligatorio hasta POSIX 2008.
R .. GitHub STOP HELPING ICE
2
De las preguntas frecuentes de Linux para lock_gettime (ver la respuesta de David Schlosnagle) "CLOCK_MONOTONIC ... es la frecuencia ajustada por NTP a través de adjtimex (). En el futuro (todavía estoy tratando de instalar el parche) habrá un CLOCK_MONOTONIC_RAW que no modificarse en absoluto, y tendrá una correlación lineal con los contadores de hardware ". No creo que el reloj _RAW haya llegado al kernel (a menos que se le cambie el nombre de _HR, pero mi investigación sugiere que también se abandonaron los esfuerzos).
Tony Delroy
41

Temporización de alta resolución y baja sobrecarga para procesadores Intel

Si tiene hardware Intel, aquí le mostramos cómo leer el contador de instrucciones en tiempo real de la CPU. Le dirá el número de ciclos de CPU ejecutados desde que se inició el procesador. Este es probablemente el contador más detallado que puede obtener para medir el rendimiento.

Tenga en cuenta que este es el número de ciclos de CPU. En Linux, puede obtener la velocidad de la CPU de / proc / cpuinfo y dividir para obtener el número de segundos. Convertir esto en un doble es bastante útil.

Cuando ejecuto esto en mi caja, obtengo

11867927879484732
11867927879692217
it took this long to call printf: 207485

Aquí está la guía para desarrolladores de Intel que brinda muchos detalles.

#include <stdio.h>
#include <stdint.h>

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}
Mark Harrison
fuente
11
Tenga en cuenta que es posible que el TSC no siempre esté sincronizado entre núcleos, que se detenga o cambie su frecuencia cuando el procesador ingrese en modos de menor potencia (y no tiene forma de saber que lo hizo) y, en general, no siempre es confiable. El kernel es capaz de detectar cuándo es confiable, detectar otras alternativas como el temporizador HPET y ACPI PM, y seleccionar automáticamente la mejor. Es una buena idea usar siempre el kernel para la sincronización, a menos que esté realmente seguro de que el TSC es estable y monótono.
CesarB
12
El TSC en las plataformas Core y superiores de Intel se sincroniza en varias CPU y se incrementa a una frecuencia constante independientemente de los estados de administración de energía. Consulte el Manual del desarrollador de software Intel, Vol. 3 Sección 18.10. Sin embargo, la velocidad a la que aumenta el contador no es la misma que la frecuencia de la CPU. El TSC se incrementa en “la frecuencia máxima resuelta de la plataforma, que es igual al producto de la frecuencia de bus escalable y la relación máxima de bus resuelta” Manual del desarrollador de software Intel, vol. 3 Sección 18.18.5. Obtiene esos valores de los registros específicos del modelo de la CPU (MSR).
sstock
7
Puede obtener la frecuencia de bus escalable y la relación de bus máxima resuelta consultando los registros específicos del modelo (MSR) de la CPU de la siguiente manera: Frecuencia de bus escalable == MSR_FSB_FREQ [2: 0] id 0xCD, relación de bus máxima resuelta == MSR_PLATFORM_ID [12: 8] identificación 0x17. Consulte Intel SDM Vol.3 Apéndice B.1 para interpretar los valores de registro. Puede utilizar msr-tools en Linux para consultar los registros. kernel.org/pub/linux/utils/cpu/msr-tools
sstock
1
¿No debería su código CPUIDvolver a usarse después de la primera RDTSCinstrucción y antes de ejecutar el código que se está comparando? De lo contrario, ¿qué impedirá que el código comparativo se ejecute antes o en paralelo con el primero RDTSCy, en consecuencia, esté subrepresentado en el RDTSCdelta?
Tony Delroy
18

@Bernardo:

Debo admitir que la mayor parte de su ejemplo se me pasó por la cabeza. Sin embargo, se compila y parece funcionar. ¿Es esto seguro para los sistemas SMP o SpeedStep?

Esa es una buena pregunta ... Creo que el código está bien. Desde un punto de vista práctico, lo usamos en mi empresa todos los días y ejecutamos una amplia gama de cajas, de 2 a 8 núcleos. Por supuesto, YMMV, etc., pero parece ser un método de sincronización confiable y de bajo costo (porque no hace un cambio de contexto al espacio del sistema).

Generalmente, cómo funciona es:

  • declare el bloque de código como ensamblador (y volátil, por lo que el optimizador lo dejará en paz).
  • ejecutar la instrucción CPUID. Además de obtener información de la CPU (con la que no hacemos nada), sincroniza el búfer de ejecución de la CPU para que los tiempos no se vean afectados por la ejecución fuera de orden.
  • ejecutar la ejecución de rdtsc (lectura de marca de tiempo). Obtiene el número de ciclos de máquina ejecutados desde que se reinició el procesador. Este es un valor de 64 bits, por lo que con las velocidades de CPU actuales, se ajustará aproximadamente cada 194 años. Curiosamente, en la referencia original de Pentium, señalan que se envuelve aproximadamente cada 5800 años.
  • el último par de líneas almacenan los valores de los registros en las variables hi y lo, y lo colocan en el valor de retorno de 64 bits.

Notas específicas:

  • La ejecución fuera de orden puede causar resultados incorrectos, por lo que ejecutamos la instrucción "cpuid" que además de darle alguna información sobre la CPU también sincroniza cualquier ejecución de instrucción fuera de orden.

  • La mayoría de los sistemas operativos sincronizan los contadores en las CPU cuando se inician, por lo que la respuesta es buena en un par de nanosegundos.

  • El comentario de hibernación probablemente sea cierto, pero en la práctica probablemente no le importen los tiempos a través de los límites de hibernación.

  • con respecto al paso de velocidad: las CPU Intel más nuevas compensan los cambios de velocidad y devuelven un recuento ajustado. Hice un escaneo rápido en algunas de las cajas en nuestra red y encontré solo una caja que no lo tenía: un Pentium 3 que ejecutaba un servidor de base de datos antiguo. (estas son cajas de Linux, así que verifiqué con: grep constant_tsc / proc / cpuinfo)

  • No estoy seguro acerca de las CPU de AMD, somos principalmente una tienda de Intel, aunque sé que algunos de nuestros gurús de sistemas de bajo nivel hicieron una evaluación de AMD.

Espero que esto satisfaga su curiosidad, es un área de programación interesante y (en mi humilde opinión) poco estudiada. ¿Sabes cuando Jeff y Joel hablaban sobre si un programador debería conocer C? Les gritaba: "Olvídense de ese material C de alto nivel ... ¡ensamblador es lo que debe aprender si quiere saber qué está haciendo la computadora!"

Mark Harrison
fuente
1
... La gente del kernel ha estado tratando de que la gente deje de usar rdtsc por un tiempo ... y generalmente evite usarlo en el kernel porque es así de poco confiable.
Spudd86
1
Como referencia, la pregunta que hice (en una respuesta separada, antes de los comentarios) fue: "Tengo que admitir que la mayor parte de su ejemplo se me pasó por la cabeza. Sin embargo, se compila y parece funcionar. ¿Es seguro para ¿Sistemas SMP o SpeedStep? "
Bernard
9

Entonces dice microsegundos explícitamente, pero dice que la resolución del reloj del sistema no está especificada. Supongo que la resolución en este contexto significa ¿cómo se incrementará la cantidad más pequeña?

La estructura de datos se define como microsegundos como unidad de medida, pero eso no significa que el reloj o el sistema operativo sea realmente capaz de medir eso con precisión.

Como han sugerido otras personas, gettimeofday()es malo porque fijar la hora puede hacer que el reloj se desvíe y desvíe el cálculo. clock_gettime(CLOCK_MONOTONIC)es lo que quieres y clock_getres()te dirá la precisión de tu reloj.

Joe Shaw
fuente
Entonces, ¿qué sucede en su código cuando gettimeofday () salta hacia adelante o hacia atrás con el horario de verano?
mpez0
3
clock_gettime está presente solo en la versión más reciente de Linux. otro sistema solo tiene gettimeofday ()
vitaly.v.ch
8

La resolución real de gettimeofday () depende de la arquitectura del hardware. Los procesadores Intel y las máquinas SPARC ofrecen temporizadores de alta resolución que miden microsegundos. Otras arquitecturas de hardware recurren al temporizador del sistema, que normalmente se establece en 100 Hz. En tales casos, la resolución de tiempo será menos precisa.

Obtuve esta respuesta de Medición de tiempo y temporizadores de alta resolución, Parte I

Codificación sin comentarios
fuente
6

Esta respuesta menciona problemas con el ajuste del reloj. Tanto sus problemas de garantía de unidades de ticks como los problemas de ajuste de tiempo se resuelven en C ++ 11 con la <chrono>biblioteca.

Se std::chrono::steady_clockgarantiza que el reloj no se ajustará y, además, avanzará a una velocidad constante en relación con el tiempo real, por lo que tecnologías como SpeedStep no deben afectarlo.

Puede obtener unidades con seguridad de tipos convirtiendo a una de las std::chrono::durationespecializaciones, como std::chrono::microseconds. Con este tipo no hay ambigüedad sobre las unidades utilizadas por el valor de tick. Sin embargo, tenga en cuenta que el reloj no necesariamente tiene esta resolución. Puede convertir una duración a attosegundos sin tener un reloj tan preciso.

bames53
fuente
4

Por mi experiencia y por lo que he leído en Internet, la respuesta es "No", no está garantizado. Depende de la velocidad de la CPU, el sistema operativo, el sabor de Linux, etc.

Codificación sin comentarios
fuente
3

La lectura del RDTSC no es confiable en los sistemas SMP, ya que cada CPU mantiene su propio contador y no se garantiza que cada contador esté sincronizado con respecto a otra CPU.

Podría sugerir que lo intentes clock_gettime(CLOCK_REALTIME). El manual posix indica que esto debe implementarse en todos los sistemas compatibles. Puede proporcionar un recuento de nanosegundos, pero probablemente querrá verificar clock_getres(CLOCK_REALTIME)su sistema para ver cuál es la resolución real.

Doug
fuente
clock_getres(CLOCK_REALTIME)no dará la resolución real. Siempre devuelve "1 ns" (un nanosegundo) cuando los hrtimers están disponibles, verifique el include/linux/hrtimer.harchivo define HIGH_RES_NSEC 1(más en stackoverflow.com/a/23044075/196561 )
osgx