É garantido que gettimeofday () tenha resolução de microssegundos?

97

Estou portando um jogo, que foi originalmente escrito para a API Win32, para o Linux (bem, portando a porta OS X da porta Win32 para o Linux).

Eu implementei QueryPerformanceCounterdando os uSeconds desde o início do processo:

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;
}

Isso, junto com QueryPerformanceFrequency()dar um 1000000 constante como a frequência, funciona bem na minha máquina , dando-me uma variável de 64 bits que contém uSecondsdesde a inicialização do programa.

Então, isso é portátil? Não quero descobrir que funciona de maneira diferente se o kernel foi compilado de uma certa maneira ou algo parecido. Eu estou bem com ele não sendo portátil para algo diferente do Linux, no entanto.

Bernard
fonte

Respostas:

57

Talvez. Mas você tem problemas maiores. gettimeofday()pode resultar em tempos incorretos se houver processos em seu sistema que alterem o cronômetro (ou seja, ntpd). Em um linux "normal", entretanto, acredito que a resolução de gettimeofday()é 10us. Ele pode avançar e retroceder no tempo, conseqüentemente, com base nos processos em execução em seu sistema. Isso efetivamente torna a resposta à sua pergunta não.

Você deve verificar clock_gettime(CLOCK_MONOTONIC)os intervalos de tempo. Ele sofre de vários problemas menores devido a coisas como sistemas multi-core e configurações de relógio externo.

Além disso, examine a clock_getres()função.

Louis Brandy
fonte
1
clock_gettime está presente apenas no Linux mais recente. outro sistema tem apenas gettimeofday ()
vitaly.v.ch
3
@ vitaly.v.ch é POSIX, então não é apenas Linux e 'novato'? até mesmo distros 'Enterprise' como Red Hat Enterprise Linux são baseadas em 2.6.18 que tem clock_gettime então não, não muito novo .. (a data da página de manual no RHEL é 12 de março de 2004, então já existe há um tempo) a menos que você falando sobre kernels REALMENTE ANTIGOS WTF, você quer dizer?
Spudd86 de
clock_gettime foi incluído no POSIX em 2001. até onde eu sei, atualmente clock_gettime () implementado no Linux 2.6 e qnx. mas o linux 2.4 é usado atualmente em muitos sistemas de produção.
vitaly.v.ch
Foi introduzido em 2001, mas não obrigatório até POSIX 2008.
R .. GitHub PARE DE AJUDAR O ICE
2
Do Linux FAQ para lock_gettime (veja a resposta de David Schlosnagle) "CLOCK_MONOTONIC ... é a frequência ajustada por NTP via adjtimex (). No futuro (ainda estou tentando obter o patch) haverá um CLOCK_MONOTONIC_RAW que não ser modificado em tudo e terá uma correlação linear com os contadores de hardware. " Eu não acho que o relógio _RAW já tenha entrado no kernel (a menos que tenha sido renomeado para _HR, mas minha pesquisa sugere que os esforços foram abandonados também).
Tony Delroy
41

Alta resolução, baixa sobrecarga para processadores Intel

Se você estiver usando um hardware Intel, veja como ler o contador de instruções da CPU em tempo real. Ele informará o número de ciclos da CPU executados desde que o processador foi inicializado. Este é provavelmente o contador mais refinado que você pode obter para medição de desempenho.

Observe que este é o número de ciclos da CPU. No Linux, você pode obter a velocidade da CPU em / proc / cpuinfo e dividir para obter o número de segundos. Converter isso em um duplo é bastante útil.

Quando eu executo isso na minha caixa, eu recebo

11867927879484732
11867927879692217
it took this long to call printf: 207485

Aqui está o guia do desenvolvedor da Intel, que fornece muitos detalhes.

#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
fonte
11
Observe que o TSC pode nem sempre estar sincronizado entre os núcleos, pode parar ou alterar sua frequência quando o processador entra em modos de energia mais baixos (e você não tem como saber disso) e, em geral, nem sempre é confiável. O kernel é capaz de detectar quando é confiável, detectar outras alternativas como HPET e ACPI PM timer e selecionar automaticamente a melhor. É uma boa idéia sempre usar o kernel para cronometragem, a menos que você tenha realmente certeza de que o TSC é estável e monotônico.
CesarB
12
O TSC nas plataformas Core e superiores da Intel é sincronizado em várias CPUs e incrementos em uma frequência constante independente dos estados de gerenciamento de energia. Consulte o Manual do Desenvolvedor de Software Intel, Vol. 3 Seção 18.10. No entanto, a taxa na qual o contador aumenta não é a mesma que a frequência da CPU. O TSC incrementa na “frequência máxima resolvida da plataforma, que é igual ao produto da frequência do barramento escalonável e relação máxima do barramento resolvido” Intel Software Developer's Manual, Vol. 3 Seção 18.18.5. Você obtém esses valores dos registros específicos do modelo da CPU (MSRs).
sstock
7
Você pode obter a frequência de barramento escalonável e a proporção máxima de barramento resolvido consultando os registros específicos do modelo da CPU (MSRs) da seguinte forma: Frequência de barramento escalonável == MSR_FSB_FREQ [2: 0] id 0xCD, relação máxima de barramento resolvida == MSR_PLATFORM_ID [12: 8] id 0x17. Consulte Intel SDM Vol.3 Apêndice B.1 para interpretar os valores de registro. Você pode usar as ferramentas msr no Linux para consultar os registros. kernel.org/pub/linux/utils/cpu/msr-tools
sstock
1
Seu código não deveria ser usado CPUIDnovamente após a primeira RDTSCinstrução e antes de executar o código que está sendo testado? Caso contrário, o que impede que o código de referência seja executado antes / em paralelo com o primeiro RDTSCe, conseqüentemente, sub-representado no RDTSCdelta?
Tony Delroy
18

@Bernard:

Tenho que admitir, a maior parte do seu exemplo passou direto pela minha cabeça. Ele compila e parece funcionar, no entanto. Isso é seguro para sistemas SMP ou SpeedStep?

Boa pergunta ... Acho que o código está ok. Do ponto de vista prático, nós o usamos na minha empresa todos os dias e operamos em uma grande variedade de caixas, tudo de 2 a 8 núcleos. Claro, YMMV, etc, mas parece ser um método de temporização confiável e de baixo overhead (porque não faz uma mudança de contexto para o espaço do sistema).

Geralmente funciona assim:

  • declara o bloco de código como assembler (e volátil, portanto o otimizador o deixará sozinho).
  • execute a instrução CPUID. Além de obter algumas informações da CPU (com as quais não fazemos nada), ele sincroniza o buffer de execução da CPU para que os tempos não sejam afetados pela execução fora de ordem.
  • execute a execução do rdtsc (leia o carimbo de data / hora). Isso busca o número de ciclos da máquina executados desde que o processador foi reiniciado. Este é um valor de 64 bits, portanto, com as velocidades atuais da CPU, ele ocorrerá a cada 194 anos ou mais. Curiosamente, na referência original do Pentium, eles observam que ele ocorre a cada 5800 anos ou mais.
  • as últimas linhas armazenam os valores dos registradores nas variáveis ​​hi e lo e os colocam no valor de retorno de 64 bits.

Notas específicas:

  • a execução fora de ordem pode causar resultados incorretos, por isso executamos a instrução "cpuid" que, além de fornecer algumas informações sobre a CPU, também sincroniza qualquer execução de instrução fora de ordem.

  • A maioria dos sistemas operacionais sincroniza os contadores nas CPUs quando eles são iniciados, então a resposta é boa em alguns nano segundos.

  • O comentário de hibernação provavelmente é verdadeiro, mas na prática você provavelmente não se preocupa com os intervalos entre os limites de hibernação.

  • sobre speedstep: CPUs Intel mais recentes compensam as mudanças de velocidade e retorna uma contagem ajustada. Eu fiz uma varredura rápida em algumas das caixas em nossa rede e encontrei apenas uma caixa que não tinha: um Pentium 3 rodando algum servidor de banco de dados antigo. (são caixas de Linux, então verifiquei com: grep constant_tsc / proc / cpuinfo)

  • Não tenho certeza sobre as CPUs AMD, somos principalmente uma loja da Intel, embora eu saiba que alguns de nossos gurus de sistemas de baixo nível fizeram uma avaliação da AMD.

Espero que isso satisfaça sua curiosidade, é uma área de programação interessante e (IMHO) pouco estudada. Você sabe quando Jeff e Joel estavam conversando sobre se um programador deveria ou não saber C? Eu estava gritando com eles, "ei, esqueça essas coisas de C de alto nível ... assembler é o que você deve aprender se quiser saber o que o computador está fazendo!"

Mark Harrison
fonte
1
... O pessoal do kernel tem tentado fazer as pessoas pararem de usar o rdtsc por um tempo ... e geralmente evitam usá-lo no kernel porque ele não é confiável.
Spudd86 de
1
Para referência, a pergunta que fiz (em uma resposta separada - antes dos comentários) foi: "Tenho que admitir, a maior parte do seu exemplo passou direto pela minha cabeça. Compila e parece funcionar, no entanto. É seguro para Sistemas SMP ou SpeedStep? "
Bernard
9

Portanto, ele diz microssegundos explicitamente, mas diz que a resolução do relógio do sistema não é especificada. Suponho que resolução neste contexto significa qual será a menor quantidade possível de incremento?

A estrutura de dados é definida como tendo microssegundos como unidade de medida, mas isso não significa que o relógio ou sistema operacional seja realmente capaz de medir isso com precisão.

Como outras pessoas sugeriram, gettimeofday()é ruim porque acertar a hora pode causar distorção do relógio e atrapalhar o cálculo. clock_gettime(CLOCK_MONOTONIC)é o que você deseja e clock_getres()lhe dirá a precisão do seu relógio.

Joe Shaw
fonte
Então, o que acontece em seu código quando gettimeofday () avança ou retrocede com o horário de verão?
mpez0
3
clock_gettime está presente apenas no Linux mais recente. outro sistema tem apenas gettimeofday ()
vitaly.v.ch
8

A resolução real de gettimeofday () depende da arquitetura de hardware. Os processadores Intel, bem como as máquinas SPARC, oferecem temporizadores de alta resolução que medem microssegundos. Outras arquiteturas de hardware recorrem ao cronômetro do sistema, que normalmente é definido como 100 Hz. Nesses casos, a resolução do tempo será menos precisa.

Obtive esta resposta na Medição de tempo e cronômetros de alta resolução, parte I

CodingWithoutComments
fonte
6

Esta resposta menciona problemas com o ajuste do relógio. Tanto seus problemas em garantir unidades de tick quanto os problemas com o tempo sendo ajustado são resolvidos em C ++ 11 com a <chrono>biblioteca.

std::chrono::steady_clockÉ garantido que o relógio não será ajustado e, além disso, avançará a uma taxa constante em relação ao tempo real, de modo que tecnologias como SpeedStep não devem afetá-lo.

Você pode obter unidades typesafe convertendo para uma das std::chrono::durationespecializações, como std::chrono::microseconds. Com esse tipo, não há ambigüidade sobre as unidades usadas pelo valor do tique. Porém, lembre-se de que o relógio não tem necessariamente esta resolução. Você pode converter uma duração em attossegundos sem realmente ter um relógio tão preciso.

bames53
fonte
4

Pela minha experiência e pelo que li na Internet, a resposta é "Não", não é garantido. Depende da velocidade da CPU, sistema operacional, tipo de Linux, etc.

CodingWithoutComments
fonte
3

A leitura do RDTSC não é confiável em sistemas SMP, uma vez que cada CPU mantém seu próprio contador e cada contador não tem garantia de sincronização em relação a outra CPU.

Eu poderia sugerir tentar clock_gettime(CLOCK_REALTIME). O manual posix indica que isso deve ser implementado em todos os sistemas compatíveis. Ele pode fornecer uma contagem de nanossegundos, mas você provavelmente desejará verificar clock_getres(CLOCK_REALTIME)em seu sistema para ver qual é a resolução real.

Doug
fonte
clock_getres(CLOCK_REALTIME)não dará a resolução real. Sempre retorna "1 ns" (um nanossegundo) quando hrtimers estão disponíveis, verifique o include/linux/hrtimer.harquivo para define HIGH_RES_NSEC 1(mais em stackoverflow.com/a/23044075/196561 )
osgx