Tempo do jogo e processadores com vários núcleos
Com as tecnologias de gerenciamento de energia se tornando mais comuns nos computadores atuais, um método comumente usado para obter tempos de CPU de alta resolução, a instrução RDTSC, pode não funcionar mais conforme o esperado. Este artigo sugere uma solução mais precisa e confiável para obter tempos de CPU de alta resolução usando as APIs do Windows QueryPerformanceCounter e QueryPerformanceFrequency.
Tela de fundo
Desde a introdução do conjunto de instruções x86 P5, muitos desenvolvedores de jogos usaram o contador de carimbo de data/hora de leitura, a instrução RDTSC, para executar o tempo de alta resolução. Os temporizadores multimídia do Windows são precisos o suficiente para processamento de som e vídeo, mas com tempos de quadro de uma dúzia de milissegundos ou menos, eles não têm resolução suficiente para fornecer informações de tempo delta. Muitos jogos ainda usam um temporizador multimídia na inicialização para estabelecer a frequência da CPU e usam esse valor de frequência para dimensionar os resultados do RDTSC para obter um tempo preciso. Devido às limitações do RDTSC, a API do Windows expõe a maneira mais correta de acessar essa funcionalidade por meio das rotinas de QueryPerformanceCounter e QueryPerformanceFrequency.
Esse uso do RDTSC para o tempo sofre destas questões fundamentais:
- Valores descontinuados. Usar o RDTSC pressupõe diretamente que o thread está sempre em execução no mesmo processador. Sistemas multiprocessadores e de núcleo duplo não garantem a sincronização de seus contadores de ciclo entre núcleos. Isso é exacerbado quando combinado com tecnologias modernas de gerenciamento de energia que ociosos e restauram vários núcleos em momentos diferentes, o que resulta em núcleos normalmente fora de sincronização. Para um aplicativo, isso geralmente resulta em falhas ou em possíveis falhas à medida que o thread salta entre os processadores e obtém valores de tempo que resultam em deltas grandes, deltas negativos ou tempo interrompido.
- Disponibilidade de hardware dedicado. O RDTSC bloqueia as informações de tempo que o aplicativo solicita para o contador de ciclo do processador. Por muitos anos, essa foi a melhor maneira de obter informações de tempo de alta precisão, mas as placas-mãe mais recentes agora estão incluindo dispositivos de tempo dedicados que fornecem informações de tempo de alta resolução sem as desvantagens do RDTSC.
- Variabilidade da frequência da CPU. A suposição geralmente é feita de que a frequência da CPU é fixa durante a vida útil do programa. No entanto, com tecnologias modernas de gerenciamento de energia, essa é uma suposição incorreta. Embora inicialmente limitada a computadores laptop e outros dispositivos móveis, a tecnologia que altera a frequência da CPU está em uso em muitos computadores desktop high-end; desabilitar sua função para manter uma frequência consistente geralmente não é aceitável para os usuários.
Recomendações
Os jogos precisam de informações precisas de tempo, mas você também precisa implementar o código de tempo de uma maneira que evite os problemas associados ao uso do RDTSC. Ao implementar o tempo de alta resolução, execute as seguintes etapas:
Use QueryPerformanceCounter e QueryPerformanceFrequency em vez de RDTSC. Essas APIs podem usar o RDTSC, mas podem usar dispositivos de tempo na placa-mãe ou alguns outros serviços do sistema que fornecem informações de tempo de alta resolução de alta qualidade. Embora o RDTSC seja muito mais rápido do que QueryPerformanceCounter, como este último é uma chamada à API, é uma API que pode ser chamada várias centenas de vezes por quadro sem nenhum impacto perceptível. (No entanto, os desenvolvedores devem tentar fazer com que seus jogos chamem QueryPerformanceCounter o mínimo possível para evitar qualquer penalidade de desempenho.)
Ao calcular deltas, os valores devem ser fixados para garantir que quaisquer bugs nos valores de tempo não causem falhas ou cálculos instáveis relacionados ao tempo. O intervalo de fixação deve ser de 0 (para evitar valores delta negativos) a algum valor razoável com base na menor taxa de quadros esperada. É provável que a fixação seja útil em qualquer depuração do seu aplicativo, mas lembre-se de ter em mente se estiver fazendo a análise de desempenho ou executando o jogo em algum modo não otimizado.
Compute todo o tempo em um único thread. A computação de tempo em vários threads, por exemplo, com cada thread associado a um processador específico, reduz consideravelmente o desempenho de sistemas de vários núcleos.
Defina esse único thread para permanecer em um único processador usando a API do Windows SetThreadAffinityMask. Normalmente, esse é o main thread do jogo. Embora QueryPerformanceCounter e QueryPerformanceFrequency normalmente se ajustem para vários processadores, bugs no BIOS ou drivers podem fazer com que essas rotinas retornem valores diferentes à medida que o thread passa de um processador para outro. Portanto, é melhor manter o thread em um único processador.
Todos os outros threads devem operar sem coletar seus próprios dados de temporizador. Não recomendamos usar um thread de trabalho para calcular o tempo, pois isso se tornará um gargalo de sincronização. Em vez disso, os threads de trabalho devem ler carimbos de data/hora do thread main e, como os threads de trabalho leem apenas carimbos de data/hora, não é necessário usar seções críticas.
Chame QueryPerformanceFrequency apenas uma vez, pois a frequência não será alterada enquanto o sistema estiver em execução.
Compatibilidade de aplicativos
Muitos desenvolvedores fizeram suposições sobre o comportamento do RDTSC ao longo de muitos anos, portanto, é bem provável que alguns aplicativos existentes apresentem problemas quando executados em um sistema com vários processadores ou núcleos devido à implementação de tempo. Esses problemas geralmente se manifestarão como falhas ou movimento em câmera lenta. Não há uma solução fácil para aplicativos que não estão cientes do gerenciamento de energia, mas há um shim existente para forçar um aplicativo a sempre ser executado em um único processador em um sistema multiprocessador.
Para criar esse shim, baixe o Kit de Ferramentas de Compatibilidade de Aplicativos da Microsoft da Compatibilidade de Aplicativos do Windows.
Usando o Administrador de Compatibilidade, parte do kit de ferramentas, crie um banco de dados do aplicativo e correções associadas. Crie um novo modo de compatibilidade para esse banco de dados e selecione a correção de compatibilidade SingleProcAffinity para forçar todos os threads do aplicativo a serem executados em um único processador/núcleo. Usando a ferramenta de linha de comando Fixpack.exe (também parte do kit de ferramentas), você pode converter esse banco de dados em um pacote instalável para instalação, teste e distribuição.
Para obter instruções sobre como usar o Administrador de Compatibilidade, consulte a documentação do kit de ferramentas. Para obter a sintaxe e exemplos de como usar Fixpack.exe, consulte sua ajuda de linha de comando.
Para obter informações orientadas ao cliente, consulte os seguintes artigos de base de dados de conhecimento da Ajuda e Suporte da Microsoft:
- Os programas que usam a função QueryPerformanceCounter podem ter um desempenho ruim no Windows Server 2003 e no Windows XP (artigo 895980)