Como medir o tempo em milissegundos usando ANSI C?

127

Usando apenas ANSI C, existe alguma maneira de medir o tempo com precisão de milissegundos ou mais? Eu estava navegando no time.h, mas só encontrei segundas funções de precisão.

corto
fonte
4
Observe a diferença entre precisão e exatidão. Você pode obter um tempo com precisão de milissegundos gastando o tempo em segundos e multiplicando por 1000, mas não adianta. As funções de precisão ms não têm necessariamente precisão ms - embora geralmente façam melhor que 1s.
Steve Jessop
2
A resposta simples é NÃO, ANSI C não suporta precisão de milissegundos ou melhor. A resposta mais complexa depende do que você está tentando fazer - francamente, toda a área é um pesadelo - mesmo que você permita o uso das funções Posix amplamente disponíveis. Você usa o termo "medida", portanto, presumo que você esteja interessado em um intervalo, e não no tempo "relógio de parede". Mas você está tentando medir um período de tempo absoluto ou uso da CPU pelo seu processo?
Dipstick
2
Só queria dizer para SOF apenas salvou minha bacon, novamente ;-)
corlettk

Respostas:

92

Não há nenhuma função ANSI C que ofereça resolução melhor que 1 segundo, mas a função POSIX gettimeofdayfornece resolução de microssegundos. A função de relógio mede apenas a quantidade de tempo que um processo passou executando e não é precisa em muitos sistemas.

Você pode usar esta função assim:

struct timeval tval_before, tval_after, tval_result;

gettimeofday(&tval_before, NULL);

// Some code you want to time, for example:
sleep(1);

gettimeofday(&tval_after, NULL);

timersub(&tval_after, &tval_before, &tval_result);

printf("Time elapsed: %ld.%06ld\n", (long int)tval_result.tv_sec, (long int)tval_result.tv_usec);

Isso retorna Time elapsed: 1.000870na minha máquina.

Robert Gamble
fonte
30
Observação: gettimeofday () não é monotônico, o que significa que pode pular (e até retroceder) se, por exemplo, sua máquina estiver tentando manter a sincronização com um servidor de horário da rede ou outra fonte de horário.
Dipstick
3
Para ser mais preciso: em ISO C99 (que eu acho que é compatível nesta parte com ANSI C) não há nem mesmo uma garantia de qualquer resolução de tempo. (ISO C99, 7.23.1p4)
Roland Illig 26/11/2010
6
Vale a pena notar que timeval::tv_usecestá sempre abaixo de um segundo, está em loop. Ou seja, para ter diferenças de tempo maiores que 1 segundo, você deve:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
Alexander Malakhov
4
@Dipstick: Mas observe que, por exemplo, o NTP nunca move o relógio para trás até que você o solicite explicitamente.
thejh
1
A lógica de subtração de tempo do @AlexanderMalakhov é timersubincorporada na função. Podemos usar tval_resultvalores (tv_sec e tv_usec) como estão.
X4444
48
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
Nick Van Brunt
fonte
CLOCKS_PER_SEC está definido como 1000000 em muitos sistemas. Imprima seu valor para ter certeza antes de usá-lo dessa maneira.
usar o seguinte comando
16
Como são relógios por segundo, não importa qual seja o valor, o valor resultante de clock () / CLOCKS_PER_SEC será em segundos (pelo menos deveria ser). Dividir por 1000 transforma isso em milissegundos.
David Young
14
De acordo com o Manual de Referência C, os valores de clock_t podem ser iniciados em torno de 36 minutos. Se você está medindo um cálculo longo, precisa estar ciente disso.
CyberSkull
4
Lembre-se também de que a divisão inteira CLOCKS_PER_SEC / 1000pode ser inexata, o que poderia afetar o resultado final (embora, na minha experiência CLOCKS_PER_SEC, sempre tenha sido um múltiplo de 1000). Fazer (1000 * clock()) / CLOCKS_PER_SECé menos suscetível à inexatidão de divisão, mas, por outro lado, é mais suscetível ao transbordamento. Apenas algumas questões a considerar.
Cornstalks
4
Isso não mede o tempo da CPU e não o tempo da parede?
Krs013
28

Eu sempre uso a função clock_gettime (), retornando a hora do relógio CLOCK_MONOTONIC. O tempo retornado é a quantidade de tempo, em segundos e nanossegundos, desde algum ponto não especificado no passado, como a inicialização do sistema na época.

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

int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
  return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
           ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

int main(int argc, char **argv)
{
  struct timespec start, end;
  clock_gettime(CLOCK_MONOTONIC, &start);

  // Some code I am interested in measuring 

  clock_gettime(CLOCK_MONOTONIC, &end);

  uint64_t timeElapsed = timespecDiff(&end, &start);
}

fonte
5
clock_gettime () não é ANSI C.
PowerApp101
1
O CLOCK_MONOTONIC também não está implementado em muitos sistemas (incluindo muitas plataformas Linux).
Dipstick
2
@ PowerApp101 Não existe uma maneira ANSI C boa / robusta de fazer isso. Muitas das outras respostas dependem do POSIX, e não da ANCI C. Dito isto, acredito que hoje. @Dipstick Hoje, acredito que a maioria das plataformas modernas [citação necessário] suporta clock_gettime(CLOCK_MONOTONIC, ...)e há até a macro de teste de recursos _POSIX_MONOTONIC_CLOCK.
Omninonsense
22

Implementando uma solução portátil

Como já foi mencionado aqui que não existe uma solução ANSI adequada com precisão suficiente para o problema de medição de tempo, quero escrever sobre as maneiras de obter uma solução de medição de tempo portátil e, se possível, de alta resolução.

Relógio monotônico x time stamp

De um modo geral, existem duas maneiras de medir o tempo:

  • relógio monotônico;
  • carimbo de data / hora atual (data).

O primeiro usa um contador de relógio monotônico (às vezes chamado de contador de ticks), que conta os ticks com uma frequência predefinida; portanto, se você tiver um valor de ticks e a frequência for conhecida, poderá converter facilmente os ticks em tempo decorrido. Na verdade, não é garantido que um relógio monotônico reflita a hora atual do sistema de qualquer forma; ele também pode contar tiques desde a inicialização do sistema. Mas garante que um relógio sempre funcione de maneira crescente, independentemente do estado do sistema. Normalmente, a frequência está ligada a uma fonte de alta resolução de hardware, por isso fornece alta precisão (depende do hardware, mas a maioria do hardware moderno não tem problemas com fontes de relógio de alta resolução).

A segunda maneira fornece um valor de hora (data) com base no valor atual do relógio do sistema. Ele também pode ter uma alta resolução, mas tem uma grande desvantagem: esse tipo de valor de hora pode ser afetado por diferentes ajustes de hora do sistema, ou seja, alteração de fuso horário, alteração de horário de verão (DST), atualização do servidor NTP, hibernação do sistema etc. em. Em algumas circunstâncias, você pode obter um valor de tempo decorrido negativo que pode levar a um comportamento indefinido. Na verdade, esse tipo de fonte de tempo é menos confiável que a primeira.

Portanto, a primeira regra na medição do intervalo de tempo é usar um relógio monotônico, se possível. Geralmente, possui alta precisão e é confiável por design.

Estratégia de fallback

Ao implementar uma solução portátil, vale a pena considerar uma estratégia de fallback: use um relógio monotônico, se disponível, e o fallback para registro de data e hora se aproximar, se não houver um relógio monotônico no sistema.

janelas

Existe um ótimo artigo chamado Adquirindo carimbos de data / hora de alta resolução no MSDN sobre medição de tempo no Windows, que descreve todos os detalhes que você precisa saber sobre o suporte de software e hardware. Para adquirir um carimbo de data / hora de alta precisão no Windows, você deve:

  • consulte uma frequência do timer (ticks por segundo) com QueryPerformanceFrequency :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER freq;    
    
    if (QueryPerformanceFrequency (&tcounter) != 0)
        freq = tcounter.QuadPart;

    A frequência do timer é fixada na inicialização do sistema, portanto, você precisa obtê-la apenas uma vez.

  • consulte o valor atual dos ticks com QueryPerformanceCounter :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    if (QueryPerformanceCounter (&tcounter) != 0)
        tick_value = tcounter.QuadPart;
  • dimensione os ticks para o tempo decorrido, ou seja, para microssegundos:

    LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);

De acordo com a Microsoft, você não deve ter problemas com essa abordagem no Windows XP e versões posteriores na maioria dos casos. Mas você também pode usar duas soluções de fallback no Windows:

  • GetTickCount fornece o número de milissegundos decorridos desde que o sistema foi iniciado. Envolve a cada 49,7 dias, portanto, tenha cuidado ao medir intervalos mais longos.
  • GetTickCount64 é uma versão de 64 bits GetTickCount, mas está disponível a partir do Windows Vista e superior.

OS X (macOS)

O OS X (macOS) possui suas próprias unidades de tempo absoluto, que representam um relógio monotônico. A melhor maneira de começar é o artigo da Apple, Perguntas e respostas técnicas QA1398: Unidades de tempo absoluto do Mach, que descreve (com os exemplos de código) como usar a API específica do Mach para obter ticks monotônicos. Também existe uma pergunta local sobre ela chamada alternativa clock_gettime no Mac OS X que, no final, pode deixar você um pouco confuso sobre o que fazer com o possível excesso de valor, porque a frequência do contador é usada na forma de numerador e denominador. Então, um pequeno exemplo de como obter o tempo decorrido:

  • obtenha o numerador e o denominador da frequência do relógio:

    #include <mach/mach_time.h>
    #include <stdint.h>
    
    static uint64_t freq_num   = 0;
    static uint64_t freq_denom = 0;
    
    void init_clock_frequency ()
    {
        mach_timebase_info_data_t tb;
    
        if (mach_timebase_info (&tb) == KERN_SUCCESS && tb.denom != 0) {
            freq_num   = (uint64_t) tb.numer;
            freq_denom = (uint64_t) tb.denom;
        }
    }

    Você precisa fazer isso apenas uma vez.

  • consulte o valor atual do tick com mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
  • dimensione os ticks para o tempo decorrido, ou seja, para microssegundos, usando numerador e denominador consultados anteriormente:

    uint64_t value_diff = tick_value - prev_tick_value;
    
    /* To prevent overflow */
    value_diff /= 1000;
    
    value_diff *= freq_num;
    value_diff /= freq_denom;

    A principal idéia para evitar um transbordamento é diminuir a escala dos ticks com a precisão desejada antes de usar o numerador e o denominador. Como a resolução inicial do timer é em nanossegundos, nós a dividimos 1000para obter microssegundos. Você pode encontrar a mesma abordagem usada no time_mac.c do Chromium . Se você realmente precisa de uma precisão de nanossegundos, considere a leitura de Como posso usar o mach_absolute_time sem transbordar? .

Linux e UNIX

A clock_gettimechamada é o seu melhor caminho em qualquer sistema compatível com POSIX. Ele pode consultar a hora de diferentes fontes de relógio, e a que precisamos é CLOCK_MONOTONIC. Nem todos os sistemas com clock_gettimesuporte CLOCK_MONOTONIC, portanto, a primeira coisa que você precisa fazer é verificar sua disponibilidade:

  • se _POSIX_MONOTONIC_CLOCKfor definido com um valor >= 0, significa que CLOCK_MONOTONICestá disponível;
  • se _POSIX_MONOTONIC_CLOCKestiver definido 0, significa que você deve verificar adicionalmente se funciona em tempo de execução, sugiro usar sysconf:

    #include <unistd.h>
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
        /* A monotonic clock presents */
    }
    #endif
  • caso contrário, um relógio monotônico não é suportado e você deve usar uma estratégia de fallback (veja abaixo).

O uso de clock_gettimeé bastante simples:

  • obtenha o valor do tempo:

    #include <time.h>
    #include <sys/time.h>
    #include <stdint.h>
    
    uint64_t get_posix_clock_time ()
    {
        struct timespec ts;
    
        if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
            return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
        else
            return 0;
    }

    Reduzi o tempo para microssegundos aqui.

  • calcule a diferença com o valor de tempo anterior recebido da mesma maneira:

    uint64_t prev_time_value, time_value;
    uint64_t time_diff;
    
    /* Initial time */
    prev_time_value = get_posix_clock_time ();
    
    /* Do some work here */
    
    /* Final time */
    time_value = get_posix_clock_time ();
    
    /* Time difference */
    time_diff = time_value - prev_time_value;

A melhor estratégia de fallback é usar a gettimeofdaychamada: não é monotônica, mas fornece uma resolução bastante boa. A idéia é a mesma que com clock_gettime, mas para obter um valor de tempo, você deve:

#include <time.h>
#include <sys/time.h>
#include <stdint.h>

uint64_t get_gtod_clock_time ()
{
    struct timeval tv;

    if (gettimeofday (&tv, NULL) == 0)
        return (uint64_t) (tv.tv_sec * 1000000 + tv.tv_usec);
    else
        return 0;
}

Novamente, o valor do tempo é reduzido para microssegundos.

SGI IRIX

O IRIX tem a clock_gettimeligação, mas falta CLOCK_MONOTONIC. Em vez disso, possui sua própria fonte de relógio monotônico definida como a CLOCK_SGI_CYCLEqual você deve usar em vez de CLOCK_MONOTONICcom clock_gettime.

Solaris e HP-UX

O Solaris possui sua própria interface de timer de alta resolução, gethrtimeque retorna o valor atual do timer em nanossegundos. Embora as versões mais recentes do Solaris possam ter clock_gettime, você pode seguirgethrtime se precisar dar suporte a versões antigas do Solaris.

O uso é simples:

#include <sys/time.h>

void time_measure_example ()
{
    hrtime_t prev_time_value, time_value;
    hrtime_t time_diff;

    /* Initial time */
    prev_time_value = gethrtime ();

    /* Do some work here */

    /* Final time */
    time_value = gethrtime ();

    /* Time difference */
    time_diff = time_value - prev_time_value;
}

O HP-UX não possui clock_gettime, mas suporta os gethrtimequais você deve usar da mesma maneira que no Solaris.

BeOS

O BeOS também possui sua própria interface de timer de alta resolução, system_timeque retorna o número de microssegundos decorridos desde que o computador foi inicializado.

Exemplo de uso:

#include <kernel/OS.h>

void time_measure_example ()
{
    bigtime_t prev_time_value, time_value;
    bigtime_t time_diff;

    /* Initial time */
    prev_time_value = system_time ();

    /* Do some work here */

    /* Final time */
    time_value = system_time ();

    /* Time difference */
    time_diff = time_value - prev_time_value;
}

OS / 2

O OS / 2 possui sua própria API para recuperar registros de data e hora de alta precisão:

  • consulte uma frequência do timer (ticks por unidade) com DosTmrQueryFreq(para compilador GCC):

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    ULONG freq;
    
    DosTmrQueryFreq (&freq);
  • consulte o valor atual dos ticks com DosTmrQueryTime:

    QWORD    tcounter;
    unit64_t time_low;
    unit64_t time_high;
    unit64_t timestamp;
    
    if (DosTmrQueryTime (&tcounter) == NO_ERROR) {
        time_low  = (unit64_t) tcounter.ulLo;
        time_high = (unit64_t) tcounter.ulHi;
    
        timestamp = (time_high << 32) | time_low;
    }
  • dimensione os ticks para o tempo decorrido, ou seja, para microssegundos:

    uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);

Implementação de exemplo

Você pode dar uma olhada na biblioteca plibsys , que implementa todas as estratégias descritas acima (consulte ptimeprofiler * .c para obter detalhes).

Alexander Saprykin
fonte
"não há solução ANSI adequada com precisão suficiente para o problema de medição do tempo": existe C11 timespec_get: stackoverflow.com/a/36095407/895245
Ciro Santilli郝海东冠状病六四事件法轮功
1
Ainda é uma maneira errada de medir o tempo de execução do código. timespec_getnão é monotônico.
precisa
11

timespec_get de C11

Retorna em nanossegundos, arredondado para a resolução da implementação.

Parece um ripoff ANSI do POSIX ' clock_gettime.

Exemplo: a printfé feito a cada 100ms no Ubuntu 15.10:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static long get_nanos(void) {
    struct timespec ts;
    timespec_get(&ts, TIME_UTC);
    return (long)ts.tv_sec * 1000000000L + ts.tv_nsec;
}

int main(void) {
    long nanos;
    long last_nanos;
    long start;
    nanos = get_nanos();
    last_nanos = nanos;
    start = nanos;
    while (1) {
        nanos = get_nanos();
        if (nanos - last_nanos > 100000000L) {
            printf("current nanos: %ld\n", nanos - start);
            last_nanos = nanos;
        }
    }
    return EXIT_SUCCESS;
}

O rascunho padrão do C11 N1570 7.27.2.5 "A função timespec_get diz":

Se base for TIME_UTC, o membro tv_sec será definido como o número de segundos desde que uma época definida pela implementação será truncada para um valor inteiro e o membro tv_nsec será definido como o número inteiro de nanossegundos, arredondado para a resolução do relógio do sistema. (321)

321) Embora um objeto struct timespec descreva tempos com resolução em nanossegundos, a resolução disponível depende do sistema e pode até ser maior que 1 segundo.

O C ++ 11 também recebeu std::chrono::high_resolution_clock: Temporizador de alta resolução entre plataformas C ++

implementação glibc 2.21

Pode ser encontrado em sysdeps/posix/timespec_get.c:

int
timespec_get (struct timespec *ts, int base)
{
  switch (base)
    {
    case TIME_UTC:
      if (__clock_gettime (CLOCK_REALTIME, ts) < 0)
        return 0;
      break;

    default:
      return 0;
    }

  return base;
}

tão claramente:

  • única TIME_UTCé atualmente suportada

  • encaminha para __clock_gettime (CLOCK_REALTIME, ts), que é uma API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    O Linux x86-64 possui uma clock_gettimechamada de sistema.

    Observe que este não é um método de micro-benchmarking à prova de falhas porque:

    • man clock_gettimediz que essa medida pode ter descontinuidades se você alterar alguma configuração de hora do sistema enquanto o programa é executado. É claro que esse deve ser um evento raro e você poderá ignorá-lo.

    • isso mede o tempo da parede; portanto, se o planejador decidir esquecer sua tarefa, ele parecerá ser executado por mais tempo.

    Por essas razões getrusage() pode ser uma ferramenta melhor de avaliação comparativa POSIX, apesar da menor precisão máxima de microssegundos.

    Mais informações em: Medir o tempo no Linux - hora x relógio x getrusage x clock_gettime x gettimeofday x timespec_get?

Ciro Santilli adicionou uma nova foto
fonte
esta é a resposta certa a partir de 2017, até o MSVC tem essa função; em termos de aferição procurar algo que lê registo chip (versões mais recentes de processadores x86 com extensões da PT, e as versões correspondentes mais recentes do Linux kernel / perf)
4

A melhor precisão possível é através do uso da instrução "rdtsc" somente no x86, que pode fornecer resolução no nível do relógio (é claro que é necessário levar em conta o custo da chamada rdtsc, que pode ser facilmente medida em inicialização do aplicativo).

O principal problema aqui é medir o número de relógios por segundo, o que não deve ser muito difícil.

Dark Shikari
fonte
3
Também pode ser necessário se preocupar com a afinidade do processador, porque em algumas máquinas você pode enviar as chamadas RDTSC para mais de um processador e seus contadores RDTSC podem não estar sincronizados.
Will Dean
1
Além disso, alguns processadores não possuem um TSC que aumenta monotonicamente - pense em modos de economia de energia que reduzem a frequência da CPU. Usando RDTSC para nada, mas muito horários curto localizadas é uma muito má ideia.
Snemarch
Btw, o desvio de núcleo mencionado por @WillDean e o uso de rdtsc para cronometrar é o motivo pelo qual vários jogos falharam em trabalhar em CPUs AMD64 multinúcleo (precoces?) - tive que limitar a afinidade de núcleo único no meu x2 4400+ para uma série de títulos.
Snemarch
2

A resposta aceita é boa o suficiente. Mas minha solução é mais simples. Apenas teste no Linux, use o gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.

Além disso gettimeofday, o tv_secé a parte do segundo e o tv_usecé microssegundos , não milissegundos .

long currentTimeMillis() {
  struct timeval time;
  gettimeofday(&time, NULL);

  return time.tv_sec * 1000 + time.tv_usec / 1000;
}

int main() {
  printf("%ld\n", currentTimeMillis());
  // wait 1 second
  sleep(1);
  printf("%ld\n", currentTimeMillis());
  return 0;
 }

Imprimir:

1522139691342 1522139692342exatamente um segundo.

LeeR
fonte
-4

Sob janelas:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);
Jichao
fonte
1
é este ansi C conforme solicitado?
precisa saber é o seguinte