Come registrare il tempo di esecuzione di un metodo esattamente in millisecondi?


222

C'è un modo per determinare quanto tempo deve essere eseguito un metodo (in millisecondi)?


2
Stai per caso chiedendo perché vuoi scoprire cosa puoi ottimizzare per renderlo più veloce?
Mike Dunlavey,

1
Sì, sto usando un UIWebView che sta caricando alcune pagine. Voglio ottimizzare il pageload controllando il tempo necessario al metodo per caricare la pagina 1 a pagina 10.
dan

2
Questo sembra essere un duplicato di questa domanda: stackoverflow.com/questions/889380/…
Brad Larson

@BradLarson Sebbene sembri essere un duplicato, l'altra domanda ha le risposte migliori, vale a dire che le risposte importanti non suggeriscono di usare (errato) NSDate ma invece spiegano bene perché NSDate è il modo sbagliato di fare per questo scopo.
Thomas Tempelmann,

Risposte:


437
NSDate *methodStart = [NSDate date];

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

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

Swift:

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)")

Facile da usare e con precisione inferiore al millisecondo.


3
@PeterWarbo NSTimeInterval è un typedef di double ed è definito come secondi - vedi developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Ben Lings

5
È possibile registrare questo valore con un% f - NSLog ("esecuzioneTime =% f", esecuzioneTime);
Tony,

1
@Tony hai dimenticato @,NSLog(@"executionTime = %f", executionTime);
John Riselvato,

6
Ho appena confrontato NSDatee mach_absolute_time()ad un livello di circa 30 ms. 27 contro 29, 36 contro 39, 43 contro 45. NSDateera più facile da usare per me e i risultati erano abbastanza simili da non disturbare mach_absolute_time().
re nevan,

5
Qualsiasi cosa basata su NSDate non è sicura per misurare il tempo trascorso perché il tempo può saltare, anche all'indietro. Un modo molto più sicuro è usare mach_absolute_time, come mostrato in molte delle altre risposte qui. Questo dovrebbe essere sottoposto a downgrade per essere un cattivo esempio. Si veda anche la risposta correlato che spiega tutto questo in modo più dettagliato: stackoverflow.com/a/30363702/43615
Thomas Tempelmann

252

Ecco due macro a una riga che utilizzo:

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

Usalo in questo modo:

TICK;

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

TOCK;

13
Haha. Mi piace!
bobmoff,

5
Ciò che lo rende così buono, è che tick-tock è una frase così memorabile che la registrazione non richiede quasi alcun pensiero.
John Riselvato,

30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])fa sì che questa risposta ritorni anche in quale funzione è stato utilizzato il timer. L'ho trovato utile se ho usato TICK TOCK per cronometrare più funzioni.
Golmschenk,

3
Ottima idea @golmschenk! Puoi anche esaminare __PRETTY_FUNCTION__e __LINE__se desideri informazioni più dettagliate.
Ron,

50

Per un tempismo dettagliato su OS X, è necessario utilizzare mach_absolute_time( )dichiarato in <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;

Naturalmente si applicano le solite avvertenze sulle misurazioni a grana fine; probabilmente è meglio invocare la routine sottoposta a test molte volte e fare la media / prendere un minimo / qualche altra forma di elaborazione.

Inoltre, tieni presente che potresti trovare più utile profilare la tua applicazione in esecuzione utilizzando uno strumento come Shark. Questo non ti fornirà informazioni esatte sui tempi, ma ti dirà quale percentuale del tempo dell'applicazione viene speso dove, che è spesso più utile (ma non sempre).


1
Cerchi di farlo funzionare in Swift ... qualche suggerimento?
zumzum,

1
"Uno semplicemente non ... si converte in Swift" - Ned Stark
stonedauwg

@zumzum Vedi la mia risposta per un esempio di come farlo in Swift.
jbg

23

C'è un comodo wrapper per mach_absolute_time()- è una CACurrentMediaTime()funzione.

A differenza NSDateo CFAbsoluteTimeGetCurrent()offset, mach_absolute_time()e CACurrentMediaTime()si basano sull'orologio host interno, una misura monatomica precisa e non sono soggetti a cambiamenti nel riferimento orario esterno, come quelli causati da fusi orari, ora legale o secondi saltanti.


objC

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

veloce

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

3
Penso che questa risposta meriti più voti. È molto meglio dell'uso NSDate.
Joe medio

12

In Swift, sto usando:

Nel mio Macros.swift ho appena aggiunto

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)")
}

ora puoi semplicemente chiamare ovunque

TICK()

// your code to be tracked

TOCK()
  • questo codice si basa sul codice di Ron tradotto in Swift, ha i crediti
  • Sto usando la data di inizio a livello globale, qualsiasi suggerimento per migliorare è il benvenuto

Questo dovrebbe essere \(-startTime.timeIntervalSinceNow)(notare il negativo)
Snowman

9

So che questo è vecchio ma anche io mi sono ritrovato a vagare di nuovo, quindi ho pensato di presentare la mia opzione qui.

La cosa migliore è controllare il mio post sul blog su questo: Timing cose in Objective-C: un cronometro

Fondamentalmente, ho scritto una lezione che smette di guardare in un modo molto semplice ma è incapsulata in modo che tu debba solo fare quanto segue:

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

E finisci con:

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

nel registro ...

Ancora una volta, controlla il mio post per un po 'di più o scaricalo qui: MMStopwatch.zip


7

Uso macro basate sulla soluzione di Ron .

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

Per righe di codice:

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

vedremo in console qualcosa come: TIME1: 0.096618


La tua risposta non è molto diversa dalla risposta di Ron e anche in qualche modo non riesco a vedere in che modo è meglio?
Trilarion

2
Non puoi usare la soluzione di @ Ron in un contesto due volte. Questo è il motivo principale per questa macro.
Sergey Teryokhin,

4

Uso un'implementazione di una classe di pagine molto minimale ispirata al codice di questo post del 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

Il suo utilizzo è molto semplice:

  • basta chiamare [DBGStopwatch start:@"slow-operation"];all'inizio
  • e poi [DBGStopwatch stop:@"slow-operation"];dopo il traguardo, per ottenere il tempo

3

È possibile ottenere un tempismo davvero preciso (secondi.parti di secondi) usando questa classe StopWatch. Utilizza il timer ad alta precisione nell'iPhone. L'uso di NSDate ti darà solo la precisione dei secondi. Questa versione è progettata specificamente per il rilascio automatico e la versione obiettiva-c. Ho anche una versione c ++ se necessario. Puoi trovare la versione c ++ qui .

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

La classe ha un stopWatchmetodo statico che restituisce un oggetto rilasciato automaticamente.

Una volta chiamato start, utilizzare il secondsmetodo per ottenere il tempo trascorso. Chiamare di startnuovo per riavviarlo. O stopper fermarlo. Puoi ancora leggere l'ora (chiamare seconds) in qualsiasi momento dopo aver chiamato stop.

Esempio in una funzione (chiamata di esecuzione temporizzata)

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

   ... do stuff

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

La tua "precisione in pochi secondi" non è corretta. Mentre l'intera parte di un NSTimeInterval è di secondi, è un doppio.
Steven Fisher,

3

Io uso questo codice:

#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

Io lo uso questo:

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

Ma non sono sicuro di CLOCKS_PER_SEC su iPhone. Potresti voler lasciarlo fuori.


2
CLOCKS_PER_SEC su iPhone è un valore estremamente impreciso.
mxcl,

1
Buono a sapersi. Userei la risposta di Matthew se dovessi farlo adesso.
David Kanarek,

2

Un esempio di temporizzazione a grana fine usando mach_absolute_time()in 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 il tuo obiettivo è scoprire cosa puoi sistemare per renderlo più veloce, è un obiettivo leggermente diverso. Misurare il tempo impiegato dalle funzioni è un buon modo per scoprire se ciò che hai fatto ha fatto la differenza, ma per scoprire cosa fare hai bisogno di una tecnica diversa. Questo è ciò che raccomando e so che puoi farlo su iPhone.

Modifica: i revisori mi hanno suggerito di elaborare la risposta, quindi sto cercando di pensare a un modo breve per dirlo.
Il tuo programma generale richiede abbastanza tempo per disturbarti. Supponiamo che siano N secondi.
Stai assumendo che puoi accelerarlo. L'unico modo per farlo è far sì che non faccia qualcosa che sta facendo in quel momento, tenendo conto di m secondi.
Inizialmente non sai cos'è quella cosa. Puoi indovinare, come fanno tutti i programmatori, ma potrebbe facilmente essere qualcos'altro. Qualunque cosa sia, ecco come puoi trovarla:

Dal momento che quella cosa, qualunque essa sia, rappresenta la frazione m / N del tempo, ciò significa che se la metti in pausa a caso la probabilità è m / N che la prenderai nell'atto di fare quella cosa. Naturalmente potrebbe fare qualcos'altro, ma mettilo in pausa e vedi cosa sta facendo.
Ora fallo di nuovo. Se lo vedi fare di nuovo la stessa cosa, puoi essere più sospettoso.

Fallo 10 volte o 20. Ora se lo vedi fare qualcosa di particolare (non importa come lo descrivi) in più pause, di cui puoi liberarti, sai due cose. Sai molto approssimativamente quale frazione di tempo ci vuole, ma sai esattamente cosa riparare.
Se vuoi anche sapere esattamente quanto tempo verrà risparmiato, è facile. Misuralo prima, riparalo e misuralo dopo. Se sei davvero deluso, esci dalla correzione.

Vedi come questo è diverso dalla misurazione? Sta trovando, non misurando . La maggior parte della profilazione si basa sulla misurazione quanto più esattamente possibile del tempo impiegato, come se fosse importante, e fa emergere il problema di identificare ciò che deve essere risolto. La profilazione non trova tutti i problemi, ma questo metodo trova tutti i problemi, ed è i problemi che non trovi che ti feriscono.


0

Ecco un altro modo, in Swift, di farlo usando la parola chiave differire

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

Dai documenti di Apple : un'istruzione di differimento viene utilizzata per eseguire il codice appena prima di trasferire il controllo del programma al di fuori dell'ambito in cui appare l'istruzione di differimento.

Questo è simile a un blocco try / finally con il vantaggio di raggruppare il codice correlato.


0

Lo uso nella mia libreria utils ( 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.")
    }
}

... quindi chiama un metodo come:

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

... che a sua volta si presenta così nella console dopo l'esecuzione:

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

Non così conciso come TICK / TOCK sopra, ma è abbastanza chiaro per vedere cosa sta facendo e include automaticamente ciò che viene cronometrato (per file, riga all'inizio del metodo e nome della funzione). Ovviamente se volevo maggiori dettagli (ad esempio, se non sto solo impostando il tempismo di una chiamata al metodo come nel solito caso, ma sto invece calcolando un blocco all'interno di quel metodo) posso aggiungere il parametro "name =" Foo "" sull'iniz di PrintTimer per nominarlo qualcosa oltre alle impostazioni predefinite.


-1

Dal momento che vuoi ottimizzare il tempo che passa da una pagina all'altra in UIWebView, non significa che stai davvero cercando di ottimizzare il Javascript usato nel caricamento di queste pagine?

A tal fine, guarderei un profiler WebKit come quello di cui abbiamo parlato qui:

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

Un altro approccio sarebbe quello di iniziare ad alto livello e pensare a come progettare le pagine Web in questione per ridurre al minimo i tempi di caricamento utilizzando il caricamento delle pagine in stile AJAX invece di aggiornare ogni volta l'intera vista web.


-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

Ecco una soluzione Swift 3 per bisecare il codice ovunque per trovare un processo di lunga durata.

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()
}

Utilizzo: -

instrument("View Did Appear")

print("ELAPSED TIMES \(elapsedTimes)")

Uscita campione: -

TEMPI ELAPSATI [MyApp.SomeViewController.Instrumentation (titolo: "Start View Did Load", punto: 1, tempo trascorso: 0.040504038333892822), MyApp.SomeViewController.Instrumentation (titolo: "Finished Adding SubViews", point: 2, elapsedTime: 0.01058550117, MyApp.SomeViewController.Instrumentation (titolo: "View Did Appear", punto: 3, tempo trascorso: 0,56564098596572876)]


-1

molte risposte sono strane e non danno risultati in millisecondi (ma in pochi secondi o altro):

ecco cosa uso per ottenere la SM (MILLISECONDI):

Swift:

let startTime = NSDate().timeIntervalSince1970 * 1000

// your Swift code

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

Objective-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

Per Swift 4, aggiungi come delegato alla tua classe:

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!))")
        }
    }
}

Aggiungi alla nostra classe:

class MyViewcontroller: UIViewController, TimingDelegate

Quindi aggiungi alla tua classe:

var _TICK: Date?

Quando vuoi cronometrare qualcosa, inizia con:

TICK

E termina con:

TOCK("Timing the XXX routine")

Hai letto le risposte e i commenti? Non usare Date per questo!
opaco
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.