Como registrar o tempo de execução de um método exatamente em milissegundos?


222

Existe uma maneira de determinar quanto tempo um método precisa executar (em milissegundos)?


2
Por acaso você está perguntando, porque deseja descobrir o que pode otimizar para torná-lo mais rápido?
Mike Dunlavey

1
Sim, estou usando um UIWebView que está carregando algumas páginas. Quero otimizar o pageloading verificando o tempo, o método precisa de carregamento da página 1 a página 10.
dan

2
Isso parece ser uma duplicata desta pergunta: stackoverflow.com/questions/889380/…
Brad Larson

@BradLarson Embora pareça duplicada, a outra pergunta tem as melhores respostas, ou seja, as respostas mais importantes não estão sugerindo o uso (incorreto) do NSDate, mas sim explicando por que o NSDate é a maneira errada de fazer isso.
Thomas Tempelmann

Respostas:


437
NSDate *methodStart = [NSDate date];

/* ... Do whatever you need to do ... */

NSDate *methodFinish = [NSDate date];
NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
NSLog(@"executionTime = %f", executionTime);

Rápido:

let methodStart = NSDate()

/* ... Do whatever you need to do ... */

let methodFinish = NSDate()
let executionTime = methodFinish.timeIntervalSinceDate(methodStart)
print("Execution time: \(executionTime)")

Swift3:

let methodStart = Date()

/* ... Do whatever you need to do ... */

let methodFinish = Date()
let executionTime = methodFinish.timeIntervalSince(methodStart)
print("Execution time: \(executionTime)")

Fácil de usar e com precisão abaixo de milissegundos.


3
@PeterWarbo NSTimeInterval é um typedef duplo e é definido como segundos - consulte developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Ben Lings

5
Você pode registrar esse valor com um% f - NSLog ("ExecutionTime =% f", ExecutionTime);
Tony

1
@ NSLog(@"executionTime = %f", executionTime);
Tony

6
Acabei de comparar NSDatee mach_absolute_time()em torno de 30ms. 27 x 29, 36 x 39, 43 x 45. NSDatefoi mais fácil de usar e os resultados foram semelhantes o suficiente para não incomodar mach_absolute_time().
Nevan king 31/07/2013

5
Qualquer coisa baseada no NSDate não é segura para medir o tempo passado, porque o tempo pode saltar, mesmo para trás. Uma maneira muito mais segura é usar mach_absolute_time, como mostrado em muitas das outras respostas aqui. Este deve ser votado por ser um mau exemplo. Ver também a resposta relacionada que explica tudo isso em mais detalhes: stackoverflow.com/a/30363702/43615
Thomas Tempelmann

252

Aqui estão duas macros de uma linha que eu uso:

#define TICK   NSDate *startTime = [NSDate date]
#define TOCK   NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])

Use-o assim:

TICK;

/* ... Do Some Work Here ... */

TOCK;

13
Haha Eu gosto disso!
bobmoff

5
O que torna isso tão bom é que tick-tock é uma frase tão memorável que o registro quase não requer reflexão.
John Riselvato

30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])faz com que esta resposta também retorne em qual função o temporizador foi usado. Achei isso útil se eu usasse o TICK TOCK para cronometrar várias funções.
golmschenk 13/03

3
Ótima idéia @golmschenk! Você também pode analisar __PRETTY_FUNCTION__e __LINE__se deseja informações mais detalhadas.
Ron

50

Para um tempo mais refinado no OS X, você deve usar mach_absolute_time( )declarado em <mach/mach_time.h>:

#include <mach/mach_time.h>
#include <stdint.h>

// Do some stuff to setup for timing
const uint64_t startTime = mach_absolute_time();
// Do some stuff that you want to time
const uint64_t endTime = mach_absolute_time();

// Time elapsed in Mach time units.
const uint64_t elapsedMTU = endTime - startTime;

// Get information for converting from MTU to nanoseconds
mach_timebase_info_data_t info;
if (mach_timebase_info(&info))
   handleErrorConditionIfYoureBeingCareful();

// Get elapsed time in nanoseconds:
const double elapsedNS = (double)elapsedMTU * (double)info.numer / (double)info.denom;

É claro que as advertências usuais sobre medições refinadas se aplicam; provavelmente é melhor invocar a rotina em teste várias vezes e calcular a média / fazer um mínimo / outra forma de processamento.

Além disso, observe que você pode achar mais útil criar um perfil do aplicativo em execução usando uma ferramenta como o Shark. Isso não fornecerá informações exatas sobre o tempo, mas informará qual porcentagem do tempo do aplicativo está sendo gasta onde, o que geralmente é mais útil (mas nem sempre).


1
Tentando fazer isso funcionar em Swift ... alguma sugestão?
Zumzum

1
"Uma coisa não simplesmente ... convertido ao Swift" - Ned Stark
stonedauwg

@zumzum Veja minha resposta para um exemplo de como fazer isso no Swift.
Jbg 15/08

23

Existe um invólucro conveniente para mach_absolute_time()- é uma CACurrentMediaTime()função.

Diferentemente NSDateou CFAbsoluteTimeGetCurrent()compensados, mach_absolute_time()e CACurrentMediaTime()são baseados no relógio interno do host, uma medida precisa e monatômica, e não estão sujeitos a alterações na referência de horário externo, como as causadas por fusos horários, horário de verão ou segundos bissextos.


ObjC

CFTimeInterval startTime = CACurrentMediaTime();
// Do your stuff here
CFTimeInterval endTime = CACurrentMediaTime();
NSLog(@"Total Runtime: %g s", endTime - startTime);

Rápido

let startTime = CACurrentMediaTime()
// Do your stuff here
let endTime = CACurrentMediaTime()
print("Total Runtime: \(endTime - startTime) s")

3
Eu acho que essa resposta merece mais votos. É muito melhor do que usar NSDate.
Joe médio

12

No Swift, estou usando:

No meu Macros.swift acabei de adicionar

var startTime = NSDate()
func TICK(){ startTime =  NSDate() }
func TOCK(function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__){
    println("\(function) Time: \(startTime.timeIntervalSinceNow)\nLine:\(line) File: \(file)")
}

agora você pode simplesmente ligar para qualquer lugar

TICK()

// your code to be tracked

TOCK()
  • esse código é baseado no código de Ron traduz para Swift, ele tem os créditos
  • Estou usando a data de início em nível global, qualquer sugestão para melhorar é bem-vinda

Este deve ser \(-startTime.timeIntervalSinceNow)(notar o negativo)
Snowman

9

Sei que é antiga, mas até me vi vagando novamente, então pensei em enviar minha própria opção aqui.

A melhor aposta é dar uma olhada no meu blog: Cronometrando as coisas no Objective-C: Um cronômetro

Basicamente, escrevi uma classe que para de assistir de uma maneira muito básica, mas é encapsulada para que você só precise fazer o seguinte:

[MMStopwatchARC start:@"My Timer"];
// your work here ...
[MMStopwatchARC stop:@"My Timer"];

E você acaba com:

MyApp[4090:15203]  -> Stopwatch: [My Timer] runtime: [0.029]

no log ...

Mais uma vez, confira meu post um pouco mais ou faça o download aqui: MMStopwatch.zip


7

Eu uso macros baseadas na solução de Ron .

#define TICK(XXX) NSDate *XXX = [NSDate date]
#define TOCK(XXX) NSLog(@"%s: %f", #XXX, -[XXX timeIntervalSinceNow])

Para linhas de código:

TICK(TIME1);
/// do job here
TOCK(TIME1);

veremos no console algo como: TIME1: 0.096618


Sua resposta não é realmente muito diferente da resposta de Ron e também, de alguma forma, não consigo ver de que maneira é melhor?
precisa saber é o seguinte

2
Você não pode usar a solução de @ Ron dentro de um contexto duas vezes. Esta é a principal razão para essas macros.
Sergey Teryokhin 25/01/16

4

Eu uso uma implementação de classe de página muito mínima, inspirada no código desta postagem do blog :

#import <mach/mach_time.h>

@interface DBGStopwatch : NSObject

+ (void)start:(NSString *)name;
+ (void)stop:(NSString *)name;

@end

@implementation DBGStopwatch

+ (NSMutableDictionary *)watches {
    static NSMutableDictionary *Watches = nil;
    static dispatch_once_t OnceToken;
    dispatch_once(&OnceToken, ^{
        Watches = @{}.mutableCopy;
    });
    return Watches;
}

+ (double)secondsFromMachTime:(uint64_t)time {
    mach_timebase_info_data_t timebase;
    mach_timebase_info(&timebase);
    return (double)time * (double)timebase.numer /
        (double)timebase.denom / 1e9;
}

+ (void)start:(NSString *)name {
    uint64_t begin = mach_absolute_time();
    self.watches[name] = @(begin);
}

+ (void)stop:(NSString *)name {
    uint64_t end = mach_absolute_time();
    uint64_t begin = [self.watches[name] unsignedLongLongValue];
    DDLogInfo(@"Time taken for %@ %g s",
              name, [self secondsFromMachTime:(end - begin)]);
    [self.watches removeObjectForKey:name];
}

@end

O uso dele é muito simples:

  • basta ligar [DBGStopwatch start:@"slow-operation"];no começo
  • e [DBGStopwatch stop:@"slow-operation"];depois do final, para ganhar tempo

3

Você pode obter um timing muito bom (segundos.parts de segundos) usando esta classe StopWatch. Ele usa o timer de alta precisão no iPhone. O uso do NSDate fornece apenas uma precisão de segundo (s). Esta versão foi projetada especificamente para liberação automática e objetivo-c. Eu tenho uma versão c ++, bem se necessário. Você pode encontrar a versão c ++ aqui .

StopWatch.h

#import <Foundation/Foundation.h>


@interface StopWatch : NSObject 
{
    uint64_t _start;
    uint64_t _stop;
    uint64_t _elapsed;
}

-(void) Start;
-(void) Stop;
-(void) StopWithContext:(NSString*) context;
-(double) seconds;
-(NSString*) description;
+(StopWatch*) stopWatch;
-(StopWatch*) init;
@end

StopWatch.m

#import "StopWatch.h"
#include <mach/mach_time.h>

@implementation StopWatch

-(void) Start
{
    _stop = 0;
    _elapsed = 0;
    _start = mach_absolute_time();
}
-(void) Stop
{
    _stop = mach_absolute_time();   
    if(_stop > _start)
    {
        _elapsed = _stop - _start;
    }
    else 
    {
        _elapsed = 0;
    }
    _start = mach_absolute_time();
}

-(void) StopWithContext:(NSString*) context
{
    _stop = mach_absolute_time();   
    if(_stop > _start)
    {
        _elapsed = _stop - _start;
    }
    else 
    {
        _elapsed = 0;
    }
    NSLog([NSString stringWithFormat:@"[%@] Stopped at %f",context,[self seconds]]);

    _start = mach_absolute_time();
}


-(double) seconds
{
    if(_elapsed > 0)
    {
        uint64_t elapsedTimeNano = 0;

        mach_timebase_info_data_t timeBaseInfo;
        mach_timebase_info(&timeBaseInfo);
        elapsedTimeNano = _elapsed * timeBaseInfo.numer / timeBaseInfo.denom;
        double elapsedSeconds = elapsedTimeNano * 1.0E-9;
        return elapsedSeconds;
    }
    return 0.0;
}
-(NSString*) description
{
    return [NSString stringWithFormat:@"%f secs.",[self seconds]];
}
+(StopWatch*) stopWatch
{
    StopWatch* obj = [[[StopWatch alloc] init] autorelease];
    return obj;
}
-(StopWatch*) init
{
    [super   init];
    return self;
}

@end

A classe possui um stopWatchmétodo estático que retorna um objeto liberado automaticamente.

Depois de ligar start, use o secondsmétodo para obter o tempo decorrido. Ligue startnovamente para reiniciá-lo. Ou stoppara pará-lo. Você ainda pode ler a hora (chamada seconds) a qualquer momento após a chamada stop.

Exemplo em uma função (chamada de tempo de execução)

-(void)SomeFunc
{
   StopWatch* stopWatch = [StopWatch stopWatch];
   [stopWatch Start];

   ... do stuff

   [stopWatch StopWithContext:[NSString stringWithFormat:@"Created %d Records",[records count]]];
}

Sua "precisão de apenas alguns segundos" está incorreta. Enquanto a parte inteira de um NSTimeInterval é segundos, é um dobro.
22617 Steven

3

Eu uso este código:

#import <mach/mach_time.h>

float TIME_BLOCK(NSString *key, void (^block)(void)) {
    mach_timebase_info_data_t info;
    if (mach_timebase_info(&info) != KERN_SUCCESS)
    {
        return -1.0;
    }

    uint64_t start = mach_absolute_time();
    block();
    uint64_t end = mach_absolute_time();
    uint64_t elapsed = end - start;

    uint64_t nanos = elapsed * info.numer / info.denom;
    float cost = (float)nanos / NSEC_PER_SEC;

    NSLog(@"key: %@ (%f ms)\n", key, cost * 1000);
    return cost;
}

2

Eu uso isso:

clock_t start, end;
double elapsed;
start = clock();

//Start code to time

//End code to time

end = clock();
elapsed = ((double) (end - start)) / CLOCKS_PER_SEC;
NSLog(@"Time: %f",elapsed);

Mas não tenho certeza sobre CLOCKS_PER_SEC no iPhone. Você pode querer deixar de fora.


2
CLOCKS_PER_SEC no iPhone é um valor impreciso.
Mxcl 13/03/11

1
Bom saber. Eu usaria a resposta de Matthew se tivesse que fazer isso agora.
David Kanarek 13/03/11

2

Um exemplo de tempo refinado usando mach_absolute_time()no Swift 4:

let start = mach_absolute_time()

// do something

let elapsedMTU = mach_absolute_time() - start
var timebase = mach_timebase_info()
if mach_timebase_info(&timebase) == 0 {
    let elapsed = Double(elapsedMTU) * Double(timebase.numer) / Double(timebase.denom)
    print("render took \(elapsed)")
}
else {
    print("timebase error")
}

2

OK, se seu objetivo é descobrir o que você pode corrigir para torná-lo mais rápido, esse é um objetivo um pouco diferente. Medir o tempo que as funções levam é uma boa maneira de descobrir se o que você fez fez a diferença, mas para descobrir o que fazer, você precisa de uma técnica diferente. É isso que eu recomendo e sei que você pode fazer isso em iPhones.

Edit: Os revisores sugeriram que eu elaborasse a resposta, então estou tentando pensar em uma maneira breve de dizê-la.
Seu programa geral leva tempo suficiente para incomodá-lo. Suponha que sejam N segundos.
Você está assumindo que pode acelerar. A única maneira de fazer isso é fazê-lo não fazer algo que está fazendo naquele tempo, contabilizando m segundos.
Você inicialmente não sabe o que é isso. Você pode adivinhar, como todos os programadores, mas poderia ser facilmente outra coisa. Seja o que for, veja como você pode encontrá-lo:

Como essa coisa, seja o que for, é responsável pela fração m / N do tempo, isso significa que, se você pausar aleatoriamente, a probabilidade é m / N de que você a capturará no ato de fazer a coisa. Claro que pode estar fazendo outra coisa, mas faça uma pausa e veja o que está fazendo.
Agora faça de novo. Se você vê fazendo a mesma coisa novamente, pode ficar mais desconfiado.

Faça 10 vezes ou 20. Agora, se você vê algo em particular (não importa como você o descreve) em várias pausas, das quais você pode se livrar, você sabe duas coisas. Você sabe muito bem qual é a fração do tempo que leva, mas sabe exatamente o que consertar.
Se você também quer saber exatamente quanto tempo será economizado, isso é fácil. Meça antes, corrija e depois. Se você está realmente desapontado, volte à solução.

Você vê como isso é diferente de medir? É encontrar, não medir . A maioria dos perfis baseia-se em medir o mais exatamente possível quanto tempo é necessário, como se isso fosse importante, e acenou com mão o problema de identificar o que precisa ser corrigido. A criação de perfil não encontra todos os problemas, mas esse método encontra todos os problemas, e são os problemas que você não acha que o machucam.


0

Aqui está outra maneira, no Swift, de fazer isso usando a palavra-chave adiar

func methodName() {
  let methodStart = Date()
  defer {
    let executionTime = Date().timeIntervalSince(methodStart)
    print("Execution time: \(executionTime)")
  }
  // do your stuff here
}

Dos documentos da Apple : Uma declaração defer é usada para executar código imediatamente antes de transferir o controle do programa para fora do escopo em que a declaração defer aparece.

Isso é semelhante a um bloco try / finalmente com a vantagem de ter o código relacionado agrupado.


0

Eu uso isso na minha biblioteca de utilitários ( Swift 4.2 ):

public class PrintTimer {
    let start = Date()
    let name: String

    public init(file: String=#file, line: Int=#line, function: String=#function, name: String?=nil) {
        let file = file.split(separator: "/").last!
        self.name = name ?? "\(file):\(line) - \(function)"
    }

    public func done() {
        let end = Date()
        print("\(self.name) took \((end.timeIntervalSinceReferenceDate - self.start.timeIntervalSinceReferenceDate).roundToSigFigs(5)) s.")
    }
}

... então chame um método como:

func myFunctionCall() {
    let timer = PrintTimer()
    // ...
    timer.done()
}

... que por sua vez se parece com isso no console após a execução:

MyFile.swift:225 - myFunctionCall() took 1.8623 s.

Não é tão conciso quanto o TICK / TOCK acima, mas é claro o suficiente para ver o que está fazendo e inclui automaticamente o que está sendo cronometrado (por arquivo, linha no início do método e nome da função). Obviamente, se eu quisesse mais detalhes (por exemplo, se eu não estiver apenas cronometrando uma chamada de método, como é o caso usual, mas estiver cronometrando um bloco nesse método), posso adicionar o parâmetro "name =" Foo "" no init PrintTimer para nomear algo além dos padrões.


-1

Como você deseja otimizar o tempo passando de uma página para outra em um UIWebView, isso não significa que você realmente deseja otimizar o Javascript usado no carregamento dessas páginas?

Para esse fim, eu examinaria um criador de perfil do WebKit como o mencionado aqui:

http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/

Outra abordagem seria começar em um nível alto e pensar em como você pode projetar as páginas da Web em questão para minimizar o tempo de carregamento usando o carregamento de páginas no estilo AJAX, em vez de atualizar a visualização da Web toda a cada vez.


-1
struct TIME {

    static var ti = mach_timebase_info()
    static var k: Double = 1
    static var mach_stamp: Double {

        if ti.denom == 0 {
            mach_timebase_info(&ti)
            k = Double(ti.numer) / Double(ti.denom) * 1e-6
        }
        return Double(mach_absolute_time()) * k
    }
    static var stamp: Double { return NSDate.timeIntervalSinceReferenceDate() * 1000 }
}

do {
    let mach_start = TIME.mach_stamp
    usleep(200000)
    let mach_diff = TIME.mach_stamp - mach_start

    let start = TIME.stamp
    usleep(200000)
    let diff = TIME.stamp - start

    print(mach_diff, diff)
}

-1

Aqui está uma solução Swift 3 para dividir o código em qualquer lugar e encontrar um processo demorado.

var increment: Int = 0

var incrementTime = NSDate()

struct Instrumentation {
    var title: String
    var point: Int
    var elapsedTime: Double

    init(_ title: String, _ point: Int, _ elapsedTime: Double) {
        self.title = title
        self.point = point
        self.elapsedTime = elapsedTime
    }
}

var elapsedTimes = [Instrumentation]()

func instrument(_ title: String) {
    increment += 1
    let incrementedTime = -incrementTime.timeIntervalSinceNow
    let newPoint = Instrumentation(title, increment, incrementedTime)
    elapsedTimes.append(newPoint)
    incrementTime = NSDate()
}

Uso: -

instrument("View Did Appear")

print("ELAPSED TIMES \(elapsedTimes)")

Saída de amostra: -

TIMES ELAPSED [MyApp.SomeViewController.Instrumentation (título: "A exibição inicial foi carregada", ponto: 1, decorridoTime: 0.040504038333892822), MyApp.SomeViewController.Instrumentation (título: "Finalização da adição de sub-visualizações", ponto: 2, tempo decorrido: 0.010585010051727) MyApp.SomeViewController.Instrumentation (título: "A exibição apareceu", ponto: 3, tempo decorrido: 0.56564098596572876)]


-1

muitas respostas são estranhas e não dão resultado em milissegundos (mas em segundos ou qualquer outra coisa):

aqui o que eu uso para obter MS (MILLISECONDS):

Rápido:

let startTime = NSDate().timeIntervalSince1970 * 1000

// your Swift code

let endTimeMinusStartTime = NSDate().timeIntervalSince1970 * 1000 - startTime
print("time code execution \(endTimeMinStartTime) ms")

Objetivo-C:

double startTime = [[NSDate date] timeIntervalSince1970] * 1000.0;

// your Objective-C code

double endTimeMinusStartTime = [[NSDate date] timeIntervalSince1970] * 1000.0 - startTime;
printf("time code execution %f ms\n", endTimeMinusStartTime );

-1

Para o Swift 4, adicione como Delegado à sua turma:

public protocol TimingDelegate: class {
    var _TICK: Date?{ get set }
}

extension TimingDelegate {
    var TICK: Date {
        _TICK = Date()
        return(_TICK)!
     }

    func TOCK(message: String)  {

        if (_TICK == nil){
            print("Call 'TICK' first!")
        }

        if (message == ""){
            print("\(Date().timeIntervalSince(_TICK!))")
        }
        else{
            print("\(message): \(Date().timeIntervalSince(_TICK!))")
        }
    }
}

Adicione à nossa turma:

class MyViewcontroller: UIViewController, TimingDelegate

Em seguida, adicione à sua turma:

var _TICK: Date?

Quando você quiser cronometrar alguma coisa, comece com:

TICK

E termine com:

TOCK("Timing the XXX routine")

Você leu as respostas e comentários? Não use a data para isso!
22418
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.