Comment enregistrer le temps d'exécution d'une méthode exactement en millisecondes?

222

Existe-t-il un moyen de déterminer le temps nécessaire à une méthode pour s'exécuter (en millisecondes)?

dan
la source
2
Êtes-vous par hasard en train de demander parce que vous voulez savoir ce que vous pouvez optimiser pour le rendre plus rapide?
Mike Dunlavey du
1
Oui, j'utilise une UIWebView qui charge certaines pages. Je veux optimiser le pageloading en vérifiant le temps nécessaire à la méthode pour charger la page 1 à la page 10.
dan
2
Cela semble être un double de cette question: stackoverflow.com/questions/889380/…
Brad Larson
@BradLarson Bien qu'il semble être un doublon, l'autre question a les meilleures réponses, c'est-à-dire que les réponses importantes ne suggèrent pas d'utiliser (la mauvaise) NSDate mais expliquent bien pourquoi NSDate est la mauvaise façon de faire à cette fin.
Thomas Tempelmann

Réponses:

437
NSDate *methodStart = [NSDate date];

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

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

Rapide:

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 à utiliser et d'une précision inférieure à la milliseconde.

Matthew McGoogan
la source
3
@PeterWarbo NSTimeInterval est un typedef de double et est défini comme secondes - voir developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Ben Lings
5
Vous pouvez enregistrer cette valeur avec un% f - NSLog ("executionTime =% f", executionTime);
Tony
1
@Tony vous avez oublié le @,NSLog(@"executionTime = %f", executionTime);
John Riselvato
6
Je viens de comparer NSDateet mach_absolute_time()à environ 30 ms. 27 contre 29, 36 contre 39, 43 contre 45. NSDateétait plus facile à utiliser pour moi et les résultats étaient suffisamment similaires pour ne pas déranger mach_absolute_time().
nevan king
5
Tout ce qui est basé sur NSDate n'est pas sûr pour mesurer le temps écoulé car le temps peut sauter, même en arrière. Une manière beaucoup plus sûre consiste à utiliser mach_absolute_time, comme indiqué dans de nombreuses autres réponses ici. Celui-ci devrait être sous-estimé pour être un mauvais exemple. Voir également la réponse connexe qui explique tout cela plus en détail: stackoverflow.com/a/30363702/43615
Thomas Tempelmann
252

Voici deux macros d'une ligne que j'utilise:

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

Utilisez-le comme ceci:

TICK;

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

TOCK;
Ron
la source
13
Haha. Je l'aime!
bobmoff
5
Ce qui rend cela si bon, c'est que tick-tock est une phrase si mémorable que la journalisation ne nécessite presque aucune réflexion.
John Riselvato
30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])fait que cette réponse retourne également dans quelle fonction la minuterie a été utilisée. J'ai trouvé cela utile si j'utilisais le TICK TOCK pour chronométrer plusieurs fonctions.
golmschenk
3
Excellente idée @golmschenk! Vous pouvez également consulter __PRETTY_FUNCTION__et __LINE__si vous souhaitez des informations plus détaillées.
Ron
50

Pour un timing précis sur OS X, vous devez utiliser mach_absolute_time( )déclaré dans <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;

Bien sûr, les mises en garde habituelles concernant les mesures à grain fin s'appliquent; il vaut probablement mieux invoquer la routine testée plusieurs fois et faire la moyenne / prendre un minimum / une autre forme de traitement.

De plus, veuillez noter qu'il peut être plus utile de profiler votre application en cours d'exécution à l'aide d'un outil comme Shark. Cela ne vous donnera pas d'informations précises sur le calendrier, mais cela vous indiquera quel pourcentage du temps de l'application est dépensé où, ce qui est souvent plus utile (mais pas toujours).

Stephen Canon
la source
1
Essayer de faire fonctionner cela dans Swift ... une suggestion?
zumzum
1
"On ne se convertit pas simplement à Swift" - Ned Stark
stonedauwg
@zumzum Voir ma réponse pour un exemple de faire cela dans Swift.
jbg
23

Il y a un emballage pratique pour mach_absolute_time()- c'est une CACurrentMediaTime()fonction.

Contrairement aux décalages NSDateou CFAbsoluteTimeGetCurrent()décalages, mach_absolute_time()et CACurrentMediaTime()sont basés sur l'horloge interne de l'hôte, une mesure précise et anatomique, et ne sont pas soumis à des changements dans la référence temporelle externe, tels que ceux causés par les fuseaux horaires, l'heure d'été ou les secondes intercalaires.


ObjC

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

Rapide

let startTime = CACurrentMediaTime()
// Do your stuff here
let endTime = CACurrentMediaTime()
print("Total Runtime: \(endTime - startTime) s")
Alex Nazarsky
la source
3
Je pense que cette réponse mérite plus de votes positifs. C'est bien mieux que d'utiliser NSDate.
Joe moyen
12

Dans Swift, j'utilise:

Dans mon Macros.swift, je viens d'ajouter

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

vous pouvez maintenant appeler n'importe où

TICK()

// your code to be tracked

TOCK()
  • ce code est basé sur le code de Ron traduit en Swift, il a les crédits
  • J'utilise la date de début au niveau mondial, toute suggestion d'amélioration est la bienvenue
Adriano Spadoni
la source
Cela devrait être \(-startTime.timeIntervalSinceNow)(remarquez le négatif)
Snowman
9

Je sais que c'est un ancien mais même je me suis retrouvé à le repasser, alors j'ai pensé soumettre ma propre option ici.

Le meilleur pari est de consulter mon article de blog à ce sujet: chronométrer les choses dans Objective-C: un chronomètre

Fondamentalement, j'ai écrit une classe qui arrête de regarder de manière très basique mais est encapsulée de sorte que vous n'avez qu'à faire ce qui suit:

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

Et vous vous retrouvez avec:

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

dans le journal ...

Encore une fois, consultez mon article pour un peu plus ou téléchargez-le ici: MMStopwatch.zip

bladnman
la source
7

J'utilise des macros basées sur la solution de Ron .

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

Pour les lignes de code:

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

nous verrons dans la console quelque chose comme: TIME1: 0.096618

Sergey Teryokhin
la source
Votre réponse n'est pas vraiment très différente de la réponse de Ron et je n'arrive pas à voir en quoi c'est mieux?
Trilarion
2
Vous ne pouvez pas utiliser deux fois la solution de @ Ron dans un même contexte. C'est la raison principale de ces macros.
Sergey Teryokhin
4

J'utilise une implémentation de classe d'une page très minimale inspirée du code de ce 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

Son utilisation est très simple:

  • il suffit d'appeler [DBGStopwatch start:@"slow-operation"];au début
  • puis [DBGStopwatch stop:@"slow-operation"];après l'arrivée, pour avoir le temps
zubko
la source
3

Vous pouvez obtenir un timing très fin (secondes, parties de secondes) en utilisant cette classe StopWatch. Il utilise la minuterie haute précision de l'iPhone. L'utilisation de NSDate n'obtiendra qu'une précision d'une seconde. Cette version est conçue spécifiquement pour autorelease et objective-c. J'ai également une version c ++ si nécessaire. Vous pouvez trouver la version c ++ ici .

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 a une stopWatchméthode statique qui renvoie un objet libéré automatiquement.

Une fois que vous appelez start, utilisez la secondsméthode pour obtenir le temps écoulé. Appelez à startnouveau pour le redémarrer. Ou stoppour l'arrêter. Vous pouvez toujours lire l'heure (appel seconds) à tout moment après l'appel stop.

Exemple dans une fonction (temporisation de l'exécution)

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

   ... do stuff

   [stopWatch StopWithContext:[NSString stringWithFormat:@"Created %d Records",[records count]]];
}
FuzzyBunnySlippers
la source
Votre «précision de quelques secondes seulement» est incorrecte. Alors que toute la partie d'un NSTimeInterval est en secondes, c'est un double.
Steven Fisher
3

J'utilise ce code:

#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
la source
2

J'utilise ceci:

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

Mais je ne suis pas sûr de CLOCKS_PER_SEC sur l'iPhone. Vous voudrez peut-être le laisser de côté.

David Kanarek
la source
2
CLOCKS_PER_SEC sur iPhone est une valeur extrêmement inexacte.
mxcl
1
Bon à savoir. J'utiliserais la réponse de Matthew si je devais le faire maintenant.
David Kanarek
2

Un exemple de synchronisation à grain fin utilisant mach_absolute_time()dans 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
la source
2

OK, si votre objectif est de trouver ce que vous pouvez corriger pour le rendre plus rapide, c'est un objectif un peu différent. Mesurer le temps que prennent les fonctions est un bon moyen de savoir si ce que vous avez fait a fait une différence, mais pour savoir quoi faire, vous avez besoin d'une technique différente. C'est ce que je recommande , et je sais que vous pouvez le faire sur les iPhones.

Edit: Les critiques ont suggéré que j'élabore la réponse, donc j'essaie de penser à une brève façon de le dire.
Votre programme global prend suffisamment de temps pour vous déranger. Supposons que ce soit N secondes.
Vous supposez que vous pouvez l'accélérer. La seule façon de le faire est de ne pas faire ce qu'il fait pendant ce temps, ce qui représente m secondes.
Vous ne savez pas au départ ce que c'est. Vous pouvez deviner, comme le font tous les programmeurs, mais cela pourrait facilement être autre chose. Quoi que ce soit, voici comment le trouver:

Puisque cette chose, quelle qu'elle soit, représente la fraction m / N du temps, cela signifie que si vous la mettez en pause au hasard, la probabilité est m / N que vous l'attrapiez en train de faire cette chose. Bien sûr, il pourrait faire autre chose, mais faites une pause et voyez ce qu'il fait.
Maintenant recommence. Si vous le voyez refaire la même chose, vous pouvez être plus méfiant.

Faites-le 10 fois, ou 20. Maintenant, si vous le voyez faire quelque chose de particulier (peu importe comment vous le décrivez) sur plusieurs pauses, dont vous pouvez vous débarrasser, vous savez deux choses. Vous savez très approximativement quelle fraction de temps cela prend, mais vous savez très exactement quoi réparer.
Si vous voulez également savoir très exactement combien de temps vous économiserez, rien de plus simple. Mesurez-le avant, réparez-le et mesurez-le après. Si vous êtes vraiment déçu, annulez le correctif.

Voyez-vous en quoi cela diffère de la mesure? C'est trouver, pas mesurer . La plupart du profilage est basé sur la mesure aussi exacte que possible du temps nécessaire, comme si c'était important, et remet en question le problème de l'identification de ce qui doit être corrigé. Le profilage ne trouve pas tous les problèmes, mais cette méthode trouve tous les problèmes, et ce sont les problèmes que vous ne trouvez pas qui vous blessent.

Mike Dunlavey
la source
0

Voici une autre façon, dans Swift, de le faire en utilisant le mot clé defer

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

À partir des documents d'Apple : une instruction defer est utilisée pour exécuter du code juste avant de transférer le contrôle du programme en dehors de la portée dans laquelle l'instruction defer apparaît.

Ceci est similaire à un bloc try / finally avec l'avantage de regrouper le code associé.

CMont
la source
0

J'utilise ceci dans ma bibliothèque d'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.")
    }
}

... puis appelez une méthode comme:

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

... qui à son tour ressemble à ceci dans la console après avoir exécuté:

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

Pas aussi concis que TICK / TOCK ci-dessus, mais il est suffisamment clair pour voir ce qu'il fait et inclut automatiquement ce qui est chronométré (par fichier, ligne au début de la méthode et nom de la fonction). Évidemment, si je voulais plus de détails (par exemple, si je ne chronomètre pas seulement un appel de méthode comme c'est le cas d'habitude, mais plutôt que je chronomètre un bloc dans cette méthode), je peux ajouter le paramètre "name =" Foo "" sur l'init PrintTimer pour le nommer quelque chose en plus des valeurs par défaut.

Tom Dibble
la source
-1

Puisque vous souhaitez optimiser le temps de passage d'une page à l'autre dans une UIWebView, cela ne signifie-t-il pas que vous cherchez vraiment à optimiser le Javascript utilisé pour charger ces pages?

À cette fin, je regarderais un profileur WebKit comme celui dont nous avons parlé ici:

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

Une autre approche consisterait à commencer à un niveau élevé et à réfléchir à la manière de concevoir les pages Web en question afin de minimiser les temps de chargement à l'aide du chargement des pages de style AJAX au lieu de rafraîchir la vue Web entière à chaque fois.

Kendall Helmstetter Gelner
la source
-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)
}
user3441734
la source
-1

Voici une solution Swift 3 pour bissecter du code n'importe où pour trouver un long processus.

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

Utilisation: -

instrument("View Did Appear")

print("ELAPSED TIMES \(elapsedTimes)")

Exemple de sortie: -

TEMPS ÉCOULÉ [MyApp.SomeViewController.Instrumentation (titre: "Start View Did Load", point: 1, elapsedTime: 0.040504038333892822), MyApp.SomeViewController.Instrumentation (titre: "Finished Adding SubViews", point: 2, elapsedTime: 0.01058501005172729 MyApp.SomeViewController.Instrumentation (titre: "View Did Appear", point: 3, elapsedTime: 0.56564098596572876)]

Markhorrocks
la source
-1

de nombreuses réponses sont étranges et ne donnent pas vraiment de résultat en millisecondes (mais en quelques secondes ou autre):

voici ce que j'utilise pour obtenir MS (MILLISECONDS):

Rapide:

let startTime = NSDate().timeIntervalSince1970 * 1000

// your Swift code

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

Objectif 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 );
user924
la source
-1

Pour Swift 4, ajoutez en tant que délégué à votre 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!))")
        }
    }
}

Ajoutez à notre classe:

class MyViewcontroller: UIViewController, TimingDelegate

Ajoutez ensuite à votre classe:

var _TICK: Date?

Lorsque vous voulez chronométrer quelque chose, commencez par:

TICK

Et terminez par:

TOCK("Timing the XXX routine")
Snickitybit
la source
Avez-vous lu les réponses et les commentaires? N'utilisez pas la date pour cela!
mat