Jak zarejestrować czas wykonania metody dokładnie w milisekundach?

222

Czy istnieje sposób na określenie, ile czasu musi wykonać metoda (w milisekundach)?

dan
źródło
2
Czy przypadkiem pytasz, ponieważ chcesz dowiedzieć się, co możesz zoptymalizować, aby przyspieszyć?
Mike Dunlavey
1
Tak, używam UIWebView, który ładuje niektóre strony. Chcę zoptymalizować ładowanie stron poprzez sprawdzenie czasu potrzebnego do załadowania strony 1 do strony 10.
dn
2
To zdaje się być duplikatem tego pytania: stackoverflow.com/questions/889380/…
Brad Larson
@BradLarson Chociaż wydaje się być duplikatem, drugie pytanie ma lepsze odpowiedzi, tzn. Tam, gdzie wybitne odpowiedzi nie sugerują użycia (niepoprawnego) NSDate, ale dobrze wyjaśnia, dlaczego NSDate jest niewłaściwym sposobem do tego celu.
Thomas Tempelmann,

Odpowiedzi:

437
NSDate *methodStart = [NSDate date];

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

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

Szybki:

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

Łatwy w użyciu i ma precyzję poniżej milisekundy.

Matthew McGoogan
źródło
3
@PeterWarbo NSTimeInterval jest typedef podwójnego i jest definiowany jako sekundy - patrz developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Ben Lings
5
Możesz zarejestrować tę wartość za pomocą% f - NSLog ("wykonanieTime =% f", wykonanieTime);
Tony
1
@ Tony zapomniałeś @,NSLog(@"executionTime = %f", executionTime);
John Riselvato
6
Właśnie porównałem NSDatei mach_absolute_time()na poziomie około 30 ms. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatebyło dla mnie łatwiejsze w użyciu, a wyniki były na tyle podobne, że nie zawracałem sobie tym głowy mach_absolute_time().
Nevan King
5
Wszystko oparte na NSDate nie jest bezpieczne do pomiaru upływu czasu, ponieważ czas może skakać, nawet do tyłu. O wiele bezpieczniejszym sposobem jest użycie mach_absolute_time, jak pokazano w wielu innych odpowiedziach tutaj. Ten należy uznać za zły przykład. Zobacz także pokrewną odpowiedź, która wyjaśnia to wszystko bardziej szczegółowo: stackoverflow.com/a/30363702/43615
Thomas Tempelmann
252

Oto dwa makra jednowierszowe, których używam:

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

Użyj tego w ten sposób:

TICK;

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

TOCK;
Ron
źródło
13
Ha ha. Lubię to!
bobmoff,
5
To, co czyni to tak dobrym, to fakt, że tik tak zapada w pamięć, że rejestrowanie prawie nie wymaga zastanowienia.
John Riselvato,
30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])sprawia, że ​​ta odpowiedź również zwraca, w której funkcji użyto timera. Uznałem to za użyteczne, jeśli użyłem TICK TOCK do pomiaru wielu funkcji.
golmschenk
3
Świetny pomysł @golmschenk! Możesz także zajrzeć __PRETTY_FUNCTION__i __LINE__uzyskać bardziej szczegółowe informacje.
Ron
50

W celu uzyskania precyzyjnego pomiaru czasu w systemie OS X należy użyć mach_absolute_time( )deklarowanego w <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;

Oczywiście obowiązują zwykłe zastrzeżenia dotyczące drobnoziarnistych pomiarów; prawdopodobnie najlepiej jest przywoływać testowaną procedurę wiele razy i uśredniać / przyjmować minimum / inną formę przetwarzania.

Ponadto pamiętaj, że bardziej przydatne może być profilowanie aplikacji przy użyciu narzędzia takiego jak Shark. Nie da ci to dokładnych informacji o czasie, ale powie ci, jaki procent czasu aplikacji spędza gdzie, co jest często bardziej przydatne (ale nie zawsze).

Stephen Canon
źródło
1
Próbujesz sprawić, żeby zadziałało w Swift ... jakieś sugestie?
zumzum
1
„Nie wystarczy ... przejść na Swift” - Ned Stark
stonedauwg
@zumzum Zobacz moją odpowiedź na przykład robienia tego w Swift.
jbg
23

Jest wygodne opakowanie mach_absolute_time()- to CACurrentMediaTime()funkcja.

W przeciwieństwie NSDatelub CFAbsoluteTimeGetCurrent()offsety, mach_absolute_time()a CACurrentMediaTime()oparte są na wewnętrznym zegarze gospodarza, precyzyjny, monoatomowego środka, i nie podlega zmianom w czasie zewnętrznych punktów odniesienia, takich jak te spowodowane przez strefy czasowe, czas letni, lub sekundy przestępne.


ObjC

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

Szybki

let startTime = CACurrentMediaTime()
// Do your stuff here
let endTime = CACurrentMediaTime()
print("Total Runtime: \(endTime - startTime) s")
Alex Nazarsky
źródło
3
Myślę, że ta odpowiedź zasługuje na więcej pochwał. To o wiele lepsze niż używanie NSDate.
średni Joe
12

W Swift używam:

W moim Macros.swift właśnie dodałem

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

możesz teraz dzwonić w dowolnym miejscu

TICK()

// your code to be tracked

TOCK()
  • ten kod jest oparty na kodzie Rona przetłumaczonym na Swift, ma kredyty
  • Używam daty rozpoczęcia na poziomie globalnym, wszelkie sugestie dotyczące poprawy są mile widziane
Adriano Spadoni
źródło
To powinno być \(-startTime.timeIntervalSinceNow)(zauważ negatywne)
Bałwan
9

Wiem, że to jest stara, ale nawet ja znów się po niej wędrowałem, więc pomyślałem, że przedstawię tutaj swoją własną opcję.

Najlepiej jest sprawdzić mój post na blogu na ten temat: Mierzenie czasu w Objective-C: Stoper

Zasadniczo napisałem klasę, która przestaje oglądać w bardzo prosty sposób, ale jest hermetyzowana, więc wystarczy wykonać następujące czynności:

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

I kończysz na:

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

w dzienniku ...

Ponownie sprawdź mój post, aby uzyskać więcej informacji lub pobierz go tutaj: MMStopwatch.zip

bladnman
źródło
7

Używam makr opartych na rozwiązaniu Rona .

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

W przypadku wierszy kodu:

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

zobaczymy w konsoli coś takiego: TIME1: 0.096618

Siergiej Teryokin
źródło
Twoja odpowiedź tak naprawdę nie różni się zbytnio od odpowiedzi Rona, a także jakoś nie rozumiem, w jaki sposób jest lepiej?
Trilarion
2
Nie możesz użyć rozwiązania @ Rona w jednym kontekście dwa razy. To jest główny powód tego makra.
Siergiej Teryokhin
4

Używam bardzo minimalnej, jednostronicowej implementacji klasy strony zainspirowanej kodem z tego postu na blogu :

#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

Korzystanie z niego jest bardzo proste:

  • po prostu zadzwoń [DBGStopwatch start:@"slow-operation"];na początku
  • a potem [DBGStopwatch stop:@"slow-operation"];po zakończeniu, aby uzyskać czas
zubko
źródło
3

Możesz uzyskać naprawdę dobry czas (sekundy. Kilka sekund) za pomocą tej klasy StopWatch. Korzysta z precyzyjnego timera w iPhonie. Korzystanie z NSDate zapewni jedynie dokładność sekundową. Ta wersja jest zaprojektowana specjalnie dla autorelease i celu-c. W razie potrzeby mam również wersję c ++. Możesz znaleźć wersję c ++ tutaj .

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

Klasa ma statyczny stopWatch metodę , która zwraca automatycznie wydany obiekt.

Po wywołaniu startużyj secondsmetody, aby uzyskać upływ czasu. Zadzwoń startponownie, aby go ponownie uruchomić. Lub stopto zatrzymać. Nadal możesz odczytać czas (połączenie seconds) w dowolnym momencie po połączeniu stop.

Przykład w funkcji A (wywołanie czasowe wykonania)

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

   ... do stuff

   [stopWatch StopWithContext:[NSString stringWithFormat:@"Created %d Records",[records count]]];
}
FuzzyBunnySlippers
źródło
Twoja dokładność „tylko sekund” jest nieprawidłowa. Chociaż cała część NSTimeInterval to sekundy, jest podwójna.
Steven Fisher
3

Używam tego kodu:

#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;
}
cleexiang
źródło
2

Używam tego:

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

Ale nie jestem pewien CLOCKS_PER_SEC na iPhonie. Może chcesz to wyłączyć.

David Kanarek
źródło
2
CLOCKS_PER_SEC na iPhonie jest bardzo niedokładną wartością.
mxcl
1
Dobrze wiedzieć. Użyłbym odpowiedzi Matthew, gdybym musiał to zrobić teraz.
David Kanarek
2

Przykład precyzyjnego pomiaru czasu mach_absolute_time()w 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")
}
jbg
źródło
2

OK, jeśli Twoim celem jest ustalenie, co możesz naprawić, aby przyspieszyć, to trochę inny cel. Mierzenie czasu potrzebnego na działanie funkcji jest dobrym sposobem, aby dowiedzieć się, czy to, co zrobiłeś, miało znaczenie, ale aby dowiedzieć się, co zrobić , potrzebujesz innej techniki. Oto co polecam i wiem, że możesz to zrobić na iPhone'ach.

Edycja: recenzenci sugerowali, że opracowałem odpowiedź, więc staram się wymyślić krótki sposób, aby to powiedzieć.
Twój ogólny program zajmuje Ci wystarczająco dużo czasu. Załóżmy, że to N sekund.
Zakładasz, że możesz to przyspieszyć. Jedynym sposobem, aby to zrobić, jest sprawienie, aby w tym czasie nie robiła czegoś, co odpowiada m sekundom.
Początkowo nie wiesz, co to jest. Możesz zgadywać, jak wszyscy programiści, ale może to być coś innego. Cokolwiek to jest, oto jak to znaleźć:

Ponieważ ta rzecz, cokolwiek to jest, stanowi ułamek m / N czasu, oznacza to, że jeśli losowo ją zatrzymasz, prawdopodobieństwo wynosi m / N , że złapiesz ją podczas robienia tego. Oczywiście może robić coś innego, ale zatrzymaj to i zobacz, co robi.
Teraz zrób to jeszcze raz. Jeśli zobaczysz, że robi to samo, możesz być bardziej podejrzliwy.

Zrób to 10 razy lub 20. Teraz, gdy zobaczysz, że robi coś szczególnego (bez względu na to, jak to opisujesz) po wielu przerwach, których możesz się pozbyć, wiesz dwie rzeczy. Wiesz z grubsza, jaki ułamek czasu zajmuje, ale bardzo dokładnie wiesz , co naprawić.
Jeśli chcesz również bardzo dokładnie wiedzieć , ile czasu zaoszczędzisz, to proste. Zmierz to wcześniej, napraw i zmierz później. Jeśli jesteś naprawdę rozczarowany, wycofaj się.

Czy widzisz, czym różni się to od pomiaru? Znajduje , nie mierzy . Większość profilowania opiera się na jak najdokładniejszym pomiarze ilości czasu, co jest ważne, i macha ręką w celu ustalenia, co należy naprawić. Profilowanie nie powoduje znalezienia każdego problemu, ale ta metoda odnajduje każdy problem i bolą Cię problemy, których nie znajdziesz.

Mike Dunlavey
źródło
0

Oto inny sposób, w Swift, aby to zrobić za pomocą słowa kluczowego odroczenia

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

Z dokumentacji Apple'a : Instrukcja odroczenia służy do wykonania kodu tuż przed przeniesieniem kontroli programu poza zakres, w którym pojawia się instrukcja odroczenia.

Jest to podobne do bloku try / wreszcie z zaletą zgrupowania powiązanego kodu.

CMont
źródło
0

Używam tego w mojej bibliotece 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.")
    }
}

... następnie wywołaj metodę taką jak:

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

... który z kolei wygląda tak w konsoli po uruchomieniu:

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

Nie tak zwięzłe jak TICK / TOCK powyżej, ale jest wystarczająco jasne, aby zobaczyć, co robi i automatycznie uwzględnia to, co jest mierzone (według pliku, linii na początku metody i nazwy funkcji). Oczywiście, jeśli chciałbym uzyskać więcej szczegółów (np. Jeśli nie tylko odmierzam czas wywołania metody, jak to zwykle bywa, ale zamiast tego mierzę czas w bloku w tej metodzie), mogę dodać parametr „name =" Foo ”do parametru PrintTimer init nazwać to coś oprócz wartości domyślnych.

Tom Dibble
źródło
-1

Skoro chcesz zoptymalizować czas przejścia z jednej strony na drugą w UIWebView, czy to nie znaczy, że naprawdę chcesz zoptymalizować JavaScript używany do ładowania tych stron?

W tym celu spojrzałbym na profilera WebKit, o którym tu mówiono:

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

Innym podejściem byłoby rozpoczęcie od wysokiego poziomu i zastanowienie się, jak zaprojektować dane strony internetowe, aby zminimalizować czas ładowania przy użyciu ładowania strony w stylu AJAX zamiast odświeżania całego widoku strony za każdym razem.

Kendall Helmstetter Gelner
źródło
-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)
}
użytkownik3441734
źródło
-1

Oto rozwiązanie Swift 3 do dzielenia kodu w dowolnym miejscu, aby znaleźć długi proces.

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

Stosowanie: -

instrument("View Did Appear")

print("ELAPSED TIMES \(elapsedTimes)")

Próbka wyjściowa: -

ELAPSED TIMES [MyApp.SomeViewController.Instrumentation (tytuł: „Start View Did Load”, punkt: 1, elapsedTime: 0.040504038333892822), MyApp.SomeViewController.Instrumentation (tytuł: „Zakończono dodawanie SubViews”, punkt: 2, upłynął: 0,02758501005 MyApp.SomeViewController.Instrumentation (tytuł: „Wyświetl pojawił się”, punkt: 3, upłynął Czas: 0,56564098596572876)]

markhorrocks
źródło
-1

wiele odpowiedzi jest dziwnych i tak naprawdę nie daje wyniku w milisekundach (ale w sekundach lub cokolwiek innego):

tutaj, czego używam, aby uzyskać MS (MILLISECONDS):

Szybki:

let startTime = NSDate().timeIntervalSince1970 * 1000

// your Swift code

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

Cel 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 );
użytkownik924
źródło
-1

W przypadku Swift 4 dodaj jako delegata do swojej klasy:

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

Dodaj do naszej klasy:

class MyViewcontroller: UIViewController, TimingDelegate

Następnie dodaj do swojej klasy:

var _TICK: Date?

Jeśli chcesz coś zrobić, zacznij od:

TICK

I zakończ z:

TOCK("Timing the XXX routine")
Snickitybit
źródło
Czy czytałeś odpowiedzi i komentarze? Nie używaj do tego daty!
mat