Como você mede o tempo que uma função leva para ser executada?
Pergunta
Como você pode medir quanto tempo uma função levará para ser executada?
Esta é uma função relativamente curta e o tempo de execução provavelmente estaria na faixa de milissegundos.
Esta questão específica refere-se a um sistema embarcado, programado em C ou C++.
Solução
A melhor maneira de fazer isso em um sistema embarcado é definir um pino de hardware externo ao entrar na função e limpá-lo ao sair da função.Isso é feito preferencialmente com um pouco de instrução de montagem para não distorcer muito os resultados.
Editar:Um dos benefícios é que você pode fazer isso em seu aplicativo real e não precisa de nenhum código de teste especial.Pinos de depuração externos como esse são (deveriam ser!) Prática padrão para todos os sistemas embarcados.
Outras dicas
Existem três soluções potenciais:
Solução de Hardware:
Use um pino de saída livre no processador e conecte um osciloscópio ou analisador lógico ao pino.Inicialize o pino para um estado baixo, pouco antes de chamar a função que deseja medir, coloque o pino em um estado alto e logo após retornar da função, desative o pino.
*io_pin = 1;
myfunc();
*io_pin = 0;
Solução para leitor ávido:
Se a função for bastante pequena e você puder gerenciar o código desmontado, poderá abrir o databook da arquitetura do processador e contar os ciclos que o processador levará para executar todas as instruções.Isso lhe dará o número de ciclos necessários.
Tempo = # ciclos * Taxa de clock do processador / tiques do relógio por instruções
Isso é mais fácil de fazer para funções menores ou código escrito em assembler (para um microcontrolador PIC, por exemplo)
Solução de contador de carimbo de data/hora:
Alguns processadores possuem um contador de carimbo de data/hora que aumenta rapidamente (a cada poucos tiques do clock do processador).Basta ler o carimbo de data/hora antes e depois da função.Isso lhe dará o tempo decorrido, mas cuidado, pois você poderá ter que lidar com a rolagem do contador.
Invoque-o em um loop com uma tonelada de invocações e depois divida pelo número de invocações para obter o tempo médio.
então:
// begin timing
for (int i = 0; i < 10000; i++) {
invokeFunction();
}
// end time
// divide by 10000 to get actual time.
se estiver usando Linux, você pode cronometrar o tempo de execução de um programa digitando na linha de comando:
time [funtion_name]
se você executar apenas a função em main() (assumindo C++), o resto do tempo do aplicativo deverá ser insignificante.
Repito a chamada de função muitas vezes (milhões), mas também emprego o seguinte método para descontar a sobrecarga do loop:
start = getTicks();
repeat n times {
myFunction();
myFunction();
}
lap = getTicks();
repeat n times {
myFunction();
}
finish = getTicks();
// overhead + function + function
elapsed1 = lap - start;
// overhead + function
elapsed2 = finish - lap;
// overhead + function + function - overhead - function = function
ntimes = elapsed1 - elapsed2;
once = ntimes / n; // Average time it took for one function call, sans loop overhead
Em vez de chamar function() duas vezes no primeiro loop e uma vez no segundo loop, você poderia chamá-lo apenas uma vez no primeiro loop e não chamá-lo (ou seja,loop vazio) no segundo, porém o loop vazio pode ser otimizado pelo compilador, fornecendo resultados de tempo negativos :)
start_time = timer
function()
exec_time = timer - start_time
Windows XP/NT incorporado ou Windows CE/Mobile
Você pode usar QueryPerformanceCounter() para obter o valor de um contador MUITO RÁPIDO antes e depois de sua função.Então você subtrai esses valores de 64 bits e obtém "tiques" delta.Usando QueryPerformanceCounterFrequency() você pode converter os "delta ticks" em uma unidade de tempo real.Você pode consultar a documentação do MSDN sobre essas chamadas WIN32.
Outros sistemas embarcados
Sem sistemas operacionais ou apenas com sistemas operacionais básicos você terá que:
- programe um dos temporizadores internos da CPU para rodar e contar livremente.
- configure-o para gerar uma interrupção quando o timer estourar, e nesta rotina de interrupção incremente uma variável "carry" (isso é para que você possa realmente medir o tempo maior que a resolução do timer escolhido).
- antes de sua função, você salva AMBOS o valor de "transporte" e o valor do registro da CPU que contém os ticks em execução para o temporizador de contagem que você configurou.
- o mesmo depois da sua função
- subtraia-os para obter um contador delta.
- a partir daí, é apenas uma questão de saber quanto tempo significa um tick em sua CPU/Hardware, considerando o relógio externo e a desmultiplicação que você configurou ao configurar seu temporizador.Você multiplica esse “comprimento do tick” pelos “tiques delta” que acabou de obter.
MUITO IMPORTANTE Não se esqueça de desabilitar antes e restaurar as interrupções depois de obter os valores do temporizador (tanto o valor de transporte quanto o valor do registro), caso contrário você corre o risco de salvar valores incorretos.
NOTAS
- Isso é muito rápido porque são necessárias apenas algumas instruções de montagem para desabilitar interrupções, salvar dois valores inteiros e reativar interrupções.A subtração e conversão reais para unidades de tempo real ocorrem FORA da zona de medição de tempo, ou seja, DEPOIS da sua função.
- Você pode querer colocar esse código em uma função para reutilizá-lo por completo, mas isso pode atrasar um pouco as coisas por causa da chamada de função e do envio de todos os registros para a pilha, mais os parâmetros, e então pop-los novamente.Em um sistema embarcado isso pode ser significativo.Pode ser melhor então em C usar MACROS ou escrever sua própria rotina de montagem salvando/restaurando apenas registros relevantes.
Depende da sua plataforma incorporada e do tipo de tempo que você está procurando.Para Linux embarcado, existem várias maneiras de fazer isso.Se desejar medir a quantidade de tempo de CPU usado pela sua função, você pode fazer o seguinte:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#define SEC_TO_NSEC(s) ((s) * 1000 * 1000 * 1000)
int work_function(int c) {
// do some work here
int i, j;
int foo = 0;
for (i = 0; i < 1000; i++) {
for (j = 0; j < 1000; j++) {
for ^= i + j;
}
}
}
int main(int argc, char *argv[]) {
struct timespec pre;
struct timespec post;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &pre);
work_function(0);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &post);
printf("time %d\n",
(SEC_TO_NSEC(post.tv_sec) + post.tv_nsec) -
(SEC_TO_NSEC(pre.tv_sec) + pre.tv_nsec));
return 0;
}
Você precisará vincular isso à biblioteca em tempo real, basta usar o seguinte para compilar seu código:
gcc -o test test.c -lrt
Você também pode ler a página de manual em clock_gettime
há alguns problemas com a execução deste código em um sistema baseado em SMP que podem invalidar o teste.Você poderia usar algo como sched_setaffinity()
ou a linha de comando cpuset
para forçar o código em apenas um núcleo.
Se você deseja medir o tempo do usuário e do sistema, você pode usar o times(NULL)
que retorna algo como um instante.Ou você pode alterar o parâmetro para clock_gettime()
de CLOCK_THREAD_CPUTIME_ID
para CLOCK_MONOTONIC
... mas tome cuidado para não enrolar CLOCK_MONOTONIC
.
Para outras plataformas, você está por conta própria.
Atraiu
Eu sempre implemento uma rotina de ticker orientada por interrupção.Isso atualiza um contador que conta o número de milissegundos desde a inicialização.Este contador é então acessado com uma função GetTickCount().
Exemplo:
#define TICK_INTERVAL 1 // milliseconds between ticker interrupts
static unsigned long tickCounter;
interrupt ticker (void)
{
tickCounter += TICK_INTERVAL;
...
}
unsigned in GetTickCount(void)
{
return tickCounter;
}
No seu código você cronometraria o código da seguinte maneira:
int function(void)
{
unsigned long time = GetTickCount();
do something ...
printf("Time is %ld", GetTickCount() - ticks);
}
No terminal OS X (e provavelmente no Unix também), use "time":
time python function.py
Se o código for .Net, use a classe cronômetro (.net 2.0+) NÃO DateTime.Now.DateTime.Now não é atualizado com precisão suficiente e fornecerá resultados malucos
Se você estiver procurando uma resolução abaixo de um milissegundo, tente um destes métodos de temporização.Todos eles fornecerão resolução em pelo menos dezenas ou centenas de microssegundos:
Se for Linux embarcado, veja os temporizadores do Linux:
http://linux.die.net/man/3/clock_gettime
Java incorporado, veja nanoTime (), embora não tenha certeza se isso está na edição incorporada:
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/System.html#nanoTime()
Se você quiser acessar os contadores de hardware, tente PAPI:
Caso contrário, você sempre pode ir para a montadora.Você pode consultar a fonte PAPI da sua arquitetura se precisar de ajuda com isso.