Red de conocimiento informático - Problemas con los teléfonos móviles - Cómo obtener la hora actual en VC (la precisión alcanza el nivel de milisegundos)

Cómo obtener la hora actual en VC (la precisión alcanza el nivel de milisegundos)

Para los desarrolladores de programas que se preocupan por el rendimiento, un buen componente de sincronización es a la vez un amigo útil y un mentor. El temporizador no solo puede usarse como un componente del programa para ayudar a los programadores a controlar con precisión el proceso del programa, sino también como una poderosa arma de depuración. En manos de programadores experimentados, puede determinar rápidamente el cuello de botella en el rendimiento del programa o tomar decisiones convincentes sobre diferentes. algoritmos. Comparación de rendimiento de fuerza.

En la plataforma Windows, hay dos temporizadores de uso común. Uno es el temporizador multimedia timeGetTime, que puede proporcionar una sincronización a nivel de milisegundos. Pero esta precisión sigue siendo demasiado aproximada para muchas aplicaciones. El otro es el contador QueryPerformanceCount, que puede proporcionar recuentos a nivel de microsegundos según el sistema. Para los programadores en procesamiento de gráficos en tiempo real, procesamiento de flujo de datos multimedia o construcción de sistemas en tiempo real, hacer un buen uso de QueryPerformanceCount/QueryPerformanceFrequency es una habilidad básica.

Lo que este artículo presentará es otro método de sincronización de alta precisión que utiliza directamente la marca de tiempo interna de la CPU Pentium para la sincronización. La siguiente discusión se beneficia principalmente del libro "Programación de gráficos de Windows", páginas 15 a 17. Los lectores interesados ​​pueden consultar el libro directamente. Para obtener una descripción detallada de las instrucciones RDTSC, consulte el manual del producto Intel. Este artículo es sólo para tirar ladrillos.

En las CPU Intel Pentium y superiores, hay un componente llamado "Marca de tiempo", que registra el tiempo desde que se encendió la CPU en el formato de un entero sin signo de 64 bits. transcurrido. Dado que la velocidad actual del reloj de la CPU es muy alta, este componente puede lograr una precisión de sincronización de nivel de nanosegundos. Esta precisión no tiene comparación con los dos métodos anteriores.

En las CPU superiores a Pentium, se proporciona una instrucción de máquina RDTSC (contador de marca de tiempo de lectura) para leer el número de esta marca de tiempo y guardarlo en el par de registros EDX:EAX. Dado que el par de registros EDX:EAX es el registro donde el lenguaje C guarda el valor de retorno de la función en la plataforma Win32, podemos considerar esta instrucción como una llamada de función ordinaria.

Así:

inline unsigned __int64 GetCycleCount()

{

__asm ​​​​RDTSC

}

Pero no funciona, porque RDTSC no es compatible directamente con el ensamblador integrado de C, por lo que necesitamos usar la pseudoinstrucción _emit para incrustar directamente el formato de código de máquina de esta instrucción 0X0F, 0X31, de la siguiente manera:

inline unsigned __int64 GetCycleCount()

{

__asm ​​​​_emit 0x0F

__asm ​​​​_emit 0x31

}

En el futuro, cuando se necesite un contador, podrá, como si utilizara la API normal de Win32, llamar a la función GetCycleCount dos veces y comparar la diferencia entre los dos valores de retorno, así:

unsigned long t;

t = (unsigned long)GetCycleCount ();

//Hacer algo que requiera mucho tiempo...

t -= ( unsigned long)GetCycleCount();

"Programación de gráficos de Windows" En la página 15, se escribe una clase para encapsular este contador. Los lectores interesados ​​pueden consultar el código de esa clase. Para lograr una sincronización más precisa, el autor realizó una pequeña mejora. El tiempo para ejecutar la instrucción RDTSC se calculó y guardó llamando a la función GetCycleCount dos veces seguidas. Después de cada vez, se calcula el recuento real. Es hora de obtener un número de sincronización más preciso. Pero personalmente creo que esta pequeña mejora tiene poca importancia. Según la medición real en mi máquina, esta instrucción toma entre decenas y más de 100 ciclos. En una máquina Celeron de 800MHz, esto es solo una décima de microsegundo. Para la mayoría de las aplicaciones, este tiempo es completamente insignificante; para aquellas aplicaciones que realmente necesitan tener una precisión del orden de nanosegundos, esta compensación es demasiado aproximada.

Las ventajas de este método son:

1. Alta precisión. Puede lograr directamente una precisión de sincronización de nivel de nanosegundos (cada ciclo de reloj es de un nanosegundo en una CPU de 1 GHz), lo cual es difícil de lograr con otros métodos de sincronización.

2. Bajo coste. La función timeGetTime necesita vincularse a la biblioteca multimedia winmm.lib. Según las instrucciones de MSDN, la función QueryPerformance* requiere soporte de hardware (aunque no he visto una máquina que no la admita) y soporte de biblioteca KERNEL, por lo que ambos pueden. solo se puede utilizar en la plataforma Windows (para problemas de sincronización de alta precisión en la plataforma DOS, puede consultar la "Guía del desarrollador del programa de gráficos", que tiene instrucciones detalladas sobre cómo controlar el temporizador 8253). Pero la instrucción RDTSC es una instrucción de CPU y es compatible con todas las máquinas Pentium y superiores bajo la plataforma i386, e incluso no hay restricciones de plataforma (creo que este método también es aplicable bajo la versión i386 de UNIX y Linux, pero las hay). no hay condiciones para realizar pruebas), y la sobrecarga de llamadas a funciones es la más pequeña.

3. Tiene una relación de velocidad que corresponde directamente a la frecuencia de la CPU.

Un conteo equivale a 1/(frecuencia de la CPU Hz) segundos, por lo que siempre que se conozca la frecuencia de la CPU, el tiempo se puede calcular directamente. Esto es diferente de QueryPerformanceCount, que necesita obtener el número de recuentos por segundo del contador actual a través de QueryPerformanceFrequency antes de convertirlo en tiempo.

Las desventajas de este método son:

1. La mayoría de los compiladores C/C existentes no admiten directamente el uso de instrucciones RDTSC y deben programarse incorporando directamente código de máquina. , que es más problemático.

2. La fluctuación de datos es bastante grave. De hecho, para cualquier método de medición, la precisión y la estabilidad son siempre una contradicción. Si se utiliza timeGetTime de baja precisión para cronometrar, el resultado será básicamente el mismo cada vez, mientras que el resultado de la instrucción RDTSC será diferente cada vez, a menudo con cientos o incluso miles de diferencias. Ésta es una contradicción inherente a la alta precisión de este enfoque.

Con respecto a la duración máxima de tiempo en este método, simplemente podemos usar la siguiente fórmula para calcular:

El número de segundos desde que se encendió la CPU = el número de ciclos leídos por tasa de frecuencia RDTSC/CPU (Hz)

El número más grande que se puede expresar mediante un entero sin signo de 64 bits es 1,8×10^19, que se puede cronometrar durante unos 700 años en mi Celeron 800 ( el libro dice que puede ser en un Pentium de 200 MHz. El tiempo es 117 años. No sé cómo se obtuvo este número (es diferente de mi cálculo). En cualquier caso, no debemos preocuparnos por el desbordamiento.

Los siguientes son algunos pequeños ejemplos para comparar brevemente el uso y la precisión de los tres métodos de sincronización.

//Timer1.cpp usa la clase Timer de la instrucción RDTSC //Definición de la clase KTimer Consulte "Programación de gráficos de Windows" P15

// Línea de compilación: CL Timer1.cpp /link USER32.lib

#include amp; p>

#include "KTimer.h"

main()

{

unsigned t

Temporizador KTimer;

temporizador.Inicio();

Dormir(1000);

t = temporizador.Parar(); Tiempo de duración: d \n", t);

}

//Timer2.cpp usa la función timeGetTime

//Necesita incluir amp;ltmmsys .hgt;, pero debido a la intrincada relación entre los archivos de encabezado de Windows

//Simplemente incluya <windows.hgt, bastante vago :)

//Línea de compilación: CL timer2.cpp / enlace winmm.lib

#include <windows.hgt

#include <stdio.hgt

main()

{

DWORD t1, t2;

t1 = tiempoGetTime();

Dormir(1000);

t2 = tiempoGetTime(); p >

printf("Hora de inicio: u\n", t1);

printf("Hora de finalización: u\n", t2); Tiempo de duración: u\n", (t2-t1));

}

//Timer3.cpp usa la función QueryPerformanceCounter

//Línea de compilación : CL timer3.cpp /link KERNEl32.lib

#include ltwindows.hgt

#include ltstdio.hgt

main()

{

LARGE_INTEGER t1, t2, tc

QueryPerformanceFrequency(amp; tc);

printf("Frecuencia: u\n", tc.QuadPart);

QueryPerformanceCounter(amp; t1);

Sleep(1000); > QueryPerformanceCounter(amp; t2);

printf("Hora de inicio: u\n", t1.QuadPart);

printf("Hora de finalización: u\n", t2); .QuadPart);

printf("Tiempo de duración: u\n", (t2.QuadPart-t1.QuadPart)); /////////////////////////////////////////////////

//Los tres programas de muestra anteriores prueban el tiempo transcurrido en 1 segundo de suspensión

file://Prueba/Entorno de prueba: Celeron 800MHz / 256M SDRAM

// Windows 2000 Professional SP2

// Microsoft Visual C 6.0 SP5

/////////////////////// / //////////////////////////

El siguiente es el resultado de la ejecución del Timer1, utilizando la instrucción RDTSC de alta precisión

Tiempo de duración: 804586872

El siguiente es el resultado de la ejecución de Timer2, utilizando la API timeGetTime más aproximada

Hora de inicio: 20254254

Hora de finalización: 20255255

Hora de duración: 1001

El siguiente es el resultado de la ejecución de Timer3, utilizando la API QueryPerformanceCount

Frecuencia: 3579545

Hora de inicio: 3804729124

Hora de finalización: 3808298836

Hora de duración: 3569712

Como decían los antiguos, se pueden sacar conclusiones por analogía. Estoy muy feliz de obtener conocimientos tan útiles sobre procesamiento en tiempo real de un libro sobre programación de gráficos. No me atrevo a ser autosuficiente. Espero que a todos les guste este temporizador ligero y eficaz tanto como a mí.