Control del tiempo de juego y procesadores de varios núcleos
Con las tecnologías de administración de energía cada vez más comunes en los equipos actuales, es posible que un método usado normalmente para obtener tiempos de CPU de alta resolución, la instrucción RDTSC ya no funcione según lo previsto. En este artículo se sugiere una solución más precisa y confiable para obtener tiempos de CPU de alta resolución mediante las API de Windows QueryPerformanceCounter y QueryPerformanceFrequency.
Información previa
Desde la introducción del conjunto de instrucciones x86 P5, muchos desarrolladores de juegos han usado el contador de marca de tiempo de lectura, la instrucción RDTSC, para realizar tiempos de alta resolución. Los temporizadores multimedia de Windows son lo suficientemente precisos para el procesamiento de sonido y vídeo, pero con tiempos de fotogramas de una docena de milisegundos o menos, no tienen suficiente resolución para proporcionar información de tiempo delta. Muchos juegos siguen usando un temporizador multimedia en el inicio para establecer la frecuencia de la CPU, y usan ese valor de frecuencia para escalar los resultados de RDTSC para obtener un tiempo preciso. Debido a las limitaciones de RDTSC, la API de Windows expone la manera más correcta de acceder a esta funcionalidad a través de las rutinas de QueryPerformanceCounter y QueryPerformanceFrequency.
Este uso de RDTSC para el tiempo sufre de estos problemas fundamentales:
- Valores discontinuos. El uso de RDTSC supone directamente que el subproceso siempre se ejecuta en el mismo procesador. Los sistemas multiprocesador y de doble núcleo no garantizan la sincronización de sus contadores de ciclo entre núcleos. Esto se agrava cuando se combina con tecnologías modernas de administración de energía que inactivan y restauran varios núcleos en momentos diferentes, lo que da lugar a que los núcleos normalmente no estén sincronizados. En el caso de una aplicación, esto suele dar lugar a errores o bloqueos potenciales a medida que el subproceso salta entre los procesadores y obtiene valores de tiempo que dan lugar a grandes diferencias, deltas negativos o tiempo detenido.
- Disponibilidad del hardware dedicado. RDTSC bloquea la información de tiempo que la aplicación solicita al contador de ciclo del procesador. Durante muchos años, esta era la mejor manera de obtener información de control de tiempo de alta precisión, pero las placas base más recientes ahora incluyen dispositivos de control de tiempo dedicados que proporcionan información de control de tiempo de alta resolución sin los inconvenientes de RDTSC.
- Variabilidad de la frecuencia de la CPU. A menudo, se supone que la frecuencia de la CPU se fija durante la vida útil del programa. Sin embargo, con las tecnologías modernas de administración de energía, se trata de una suposición incorrecta. Aunque inicialmente se limitaba a equipos portátiles y otros dispositivos móviles, la tecnología que cambia la frecuencia de la CPU está en uso en muchos equipos de escritorio de alto nivel; deshabilitar su función para mantener una frecuencia coherente no suele ser aceptable para los usuarios.
Recomendaciones
Los juegos necesitan información de tiempo precisa, pero también debes implementar código de control de tiempo de una manera que evite los problemas asociados con el uso de RDTSC. Al implementar el tiempo de alta resolución, siga estos pasos:
Use QueryPerformanceCounter y QueryPerformanceFrequency en lugar de RDTSC. Estas API pueden hacer uso de RDTSC, pero en su lugar usar un dispositivo de control de tiempo en la placa base o algunos otros servicios del sistema que proporcionan información de tiempo de alta resolución de alta calidad. Aunque RDTSC es mucho más rápido que QueryPerformanceCounter, ya que este último es una llamada API, es una API que se puede llamar varios cientos de veces por fotograma sin ningún impacto notable. (Sin embargo, los desarrolladores deben intentar hacer que sus juegos llamen a QueryPerformanceCounter lo menos posible para evitar cualquier penalización de rendimiento).
Al calcular deltas, los valores se deben fijar para asegurarse de que los errores de los valores de tiempo no provocan bloqueos ni cálculos relacionados con el tiempo inestables. El intervalo de abrazadera debe ser de 0 (para evitar valores diferenciales negativos) a un valor razonable en función de la velocidad de fotogramas más baja esperada. Es probable que la fijación sea útil en cualquier depuración de la aplicación, pero asegúrese de tenerlo en cuenta si realiza análisis de rendimiento o ejecuta el juego en algún modo no optimizado.
Calcule todo el tiempo en un único subproceso. El cálculo de tiempo en varios subprocesos (por ejemplo, con cada subproceso asociado a un procesador específico) reduce considerablemente el rendimiento de los sistemas de varios núcleos.
Establezca ese único subproceso para que permanezca en un único procesador mediante setThreadAffinityMask de la API de Windows. Normalmente, este es el subproceso principal del juego. Aunque QueryPerformanceCounter y QueryPerformanceFrequency normalmente se ajustan para varios procesadores, los errores en el BIOS o los controladores pueden dar lugar a que estas rutinas devuelvan valores diferentes a medida que el subproceso se mueve de un procesador a otro. Por lo tanto, es mejor mantener el subproceso en un solo procesador.
Todos los demás subprocesos deben funcionar sin recopilar sus propios datos de temporizador. No se recomienda usar un subproceso de trabajo para calcular el tiempo, ya que esto se convertirá en un cuello de botella de sincronización. En su lugar, los subprocesos de trabajo deben leer marcas de tiempo del subproceso principal y, dado que los subprocesos de trabajo solo leen marcas de tiempo, no es necesario usar secciones críticas.
Llame a QueryPerformanceFrequency solo una vez, ya que la frecuencia no cambiará mientras se ejecuta el sistema.
Compatibilidad de aplicaciones
Muchos desarrolladores han hecho suposiciones sobre el comportamiento de RDTSC durante muchos años, por lo que es bastante probable que algunas aplicaciones existentes muestren problemas cuando se ejecutan en un sistema con varios procesadores o núcleos debido a la implementación de tiempo. Estos problemas normalmente se manifiestan como movimiento de movimiento de movimiento lento o de brillo. No hay ninguna solución fácil para las aplicaciones que no son conscientes de la administración de energía, pero hay una corrección existente para forzar que una aplicación siempre se ejecute en un único procesador en un sistema multiprocesador.
Para crear esta corrección de compatibilidad, descargue el Kit de herramientas de compatibilidad de aplicaciones de Microsoft de Compatibilidad de aplicaciones de Windows.
Con el administrador de compatibilidad, parte del kit de herramientas, cree una base de datos de la aplicación y correcciones asociadas. Cree un nuevo modo de compatibilidad para esta base de datos y seleccione la corrección de compatibilidad SingleProcAffinity para forzar que todos los subprocesos de la aplicación se ejecuten en un único procesador o núcleo. Mediante el uso de la herramienta de línea de comandos Fixpack.exe (también parte del kit de herramientas), puede convertir esta base de datos en un paquete instalable para la instalación, las pruebas y la distribución.
Para obtener instrucciones sobre el uso del administrador de compatibilidad, consulte la documentación del kit de herramientas. Para obtener información sobre la sintaxis y ejemplos de uso de Fixpack.exe, consulte la ayuda de la línea de comandos.
Para obtener información orientada al cliente, consulte los siguientes artículos de knowledge base de ayuda y soporte técnico de Microsoft:
- Los programas que usan la función QueryPerformanceCounter pueden funcionar mal en Windows Server 2003 y en Windows XP (artículo 895980)