¿Cómo registrar el tiempo de ejecución de un método exactamente en milisegundos?

222

¿Hay alguna manera de determinar cuánto tiempo necesita ejecutar un método (en milisegundos)?

dan
fuente
2
¿Está por casualidad preguntando porque quiere saber qué puede optimizar para hacerlo más rápido?
Mike Dunlavey
1
Sí, estoy usando un UIWebView que está cargando algunas páginas. Quiero optimizar la carga de la página comprobando el tiempo que necesita el método para cargar la página 1 a la página 10.
Dan
2
Esto parece ser un duplicado de esta pregunta: stackoverflow.com/questions/889380/…
Brad Larson
@BradLarson Si bien parece ser un duplicado, la otra pregunta tiene las mejores respuestas, es decir, allí las respuestas prominentes no sugieren usar (incorrecto) NSDate, sino que explica bien por qué NSDate es la forma incorrecta de hacerlo para este propósito.
Thomas Tempelmann

Respuestas:

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 y tiene una precisión inferior a milisegundos.

Matthew McGoogan
fuente
3
@PeterWarbo NSTimeInterval es un typedef de doble y se define como segundos - vea developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Ben Lings
55
Puede registrar este valor con un% f - NSLog ("executeTime =% f", executeTime);
Tony
1
@ Tony olvidaste el @,NSLog(@"executionTime = %f", executionTime);
John Riselvato
66
Acabo de comparar NSDatey mach_absolute_time()en alrededor de 30 ms de nivel. 27 vs. 29, 36 vs. 39, 43 vs. 45. NSDatefue más fácil de usar para mí y los resultados fueron lo suficientemente similares como para no molestarme mach_absolute_time().
nevan king
55
Cualquier cosa basada en NSDate no es segura para medir el tiempo pasado porque el tiempo puede saltar, incluso hacia atrás. Una forma mucho más segura es usar mach_absolute_time, como se muestra en muchas de las otras respuestas aquí. Este debería ser rechazado por ser un mal ejemplo. Vea también la respuesta relacionada que explica todo esto con más detalle: stackoverflow.com/a/30363702/43615
Thomas Tempelmann
252

Aquí hay dos macros de una línea que uso:

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

Úselo así:

TICK;

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

TOCK;
Ron
fuente
13
Jaja. ¡Me gusta!
bobmoff
55
Lo que lo hace tan bueno es que el tic-tac es una frase tan memorable que el registro casi no requiere pensar.
John Riselvato
30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])hace que esta respuesta también devuelva en qué función se usó el temporizador. Encontré esto útil si utilicé TICK TOCK para cronometrar múltiples funciones.
golmschenk
3
¡Gran idea @golmschenk! También puede buscar __PRETTY_FUNCTION__y __LINE__si desea información más detallada.
Ron
50

Para una sincronización precisa en OS X, debe usar mach_absolute_time( )declarado en <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;

Por supuesto, se aplican las advertencias habituales sobre las mediciones de grano fino; Probablemente sea mejor invocar la rutina bajo prueba muchas veces y promediar / tomar un mínimo / alguna otra forma de procesamiento.

Además, tenga en cuenta que puede resultarle más útil perfilar su aplicación que se ejecuta con una herramienta como Shark. Esto no le dará información de tiempo exacta, pero le dirá qué porcentaje del tiempo de la aplicación se está gastando dónde, que a menudo es más útil (pero no siempre).

Stephen Canon
fuente
1
Intentando que esto funcione en Swift ... ¿alguna sugerencia?
zumzum
1
"Uno no simplemente ... se convierte a Swift" - Ned Stark
stonedauwg
@zumzum Vea mi respuesta para ver un ejemplo de cómo hacer esto en Swift.
jbg
23

Hay un contenedor conveniente para mach_absolute_time()- es una CACurrentMediaTime()función.

A diferencia NSDateo CFAbsoluteTimeGetCurrent()compensaciones, mach_absolute_time()y CACurrentMediaTime()están basados en el reloj interno de acogida, a, medida monoatómico preciso, y no está sujeto a cambios en la referencia de tiempo externa, tales como las causadas por las zonas de tiempo, los cambios de horario, o segundos bisiestos.


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")
Alex Nazarsky
fuente
3
Creo que esta respuesta merece más votos a favor. Es mucho mejor que usar NSDate.
promedio Joe
12

En Swift, estoy usando:

En mi Macros.swift acabo de agregar

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

ahora puedes llamar a cualquier parte

TICK()

// your code to be tracked

TOCK()
  • este código se basa en el código de Ron traducido a Swift, él tiene los créditos
  • Estoy usando la fecha de inicio a nivel global, cualquier sugerencia para mejorar es bienvenida
Adriano Spadoni
fuente
Esto debería ser \(-startTime.timeIntervalSinceNow) (nótese la negativa)
muñeco de nieve
9

Sé que este es viejo, pero incluso me encontré deambulando de nuevo, así que pensé en presentar mi propia opción aquí.

La mejor apuesta es revisar mi publicación de blog sobre esto: Cronometraje de cosas en Objective-C: Un cronómetro

Básicamente, escribí una clase que deja de mirar de una manera muy básica pero está encapsulada de modo que solo necesita hacer lo siguiente:

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

Y terminas con:

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

en el registro ...

Nuevamente, mira mi publicación para un poco más o descárgala aquí: MMStopwatch.zip

bladnman
fuente
7

Yo uso macros basadas en la solución de Ron .

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

Para líneas de código:

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

veremos en la consola algo como: TIME1: 0.096618

Sergey Teryokhin
fuente
Su respuesta no es realmente muy diferente de la respuesta de Ron y también de alguna manera no veo de qué manera es mejor.
Trilarion
2
No puede usar la solución de @ Ron dentro de un contexto dos veces. Esta es la razón principal de estas macros.
Sergey Teryokhin el
4

Utilizo una implementación mínima de clase de una página inspirada en el código de esta publicación de 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

Su uso es muy simple:

  • solo llama [DBGStopwatch start:@"slow-operation"];al principio
  • y luego, [DBGStopwatch stop:@"slow-operation"];después del final, para tener el tiempo
zubko
fuente
3

Puede obtener una sincronización realmente buena (segundos, partes de segundos) utilizando esta clase StopWatch. Utiliza el temporizador de alta precisión en el iPhone. El uso de NSDate solo le brindará una segunda (s) precisión (s). Esta versión está diseñada específicamente para la liberación automática y el objetivo-c. Tengo una versión de C ++ también si es necesario. Puede encontrar la versión de c ++ aquí .

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 clase tiene una estática. stopWatch método que devuelve un objeto lanzado automáticamente.

Una vez que llame start, use el secondsmétodo para obtener el tiempo transcurrido. Llame startnuevamente para reiniciarlo. O stoppara detenerlo. Aún puede leer la hora (llamada seconds) en cualquier momento después de llamar stop.

Ejemplo en una función (llamada de tiempo de ejecución)

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

   ... do stuff

   [stopWatch StopWithContext:[NSString stringWithFormat:@"Created %d Records",[records count]]];
}
FuzzyBunnySlippers
fuente
Su "precisión de solo segundos" es incorrecta. Si bien toda la parte de un NSTimeInterval es segundos, es un doble.
Steven Fisher
3

Yo 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;
}
cleexiang
fuente
2

Yo uso esto:

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

Pero no estoy seguro acerca de CLOCKS_PER_SEC en el iPhone. Es posible que desee dejarlo.

David Kanarek
fuente
2
CLOCKS_PER_SEC en iPhone es un valor muy inexacto.
mxcl
1
Bueno saber. Usaría la respuesta de Matthew si tuviera que hacer esto ahora.
David Kanarek
2

Un ejemplo de sincronización precisa utilizando mach_absolute_time()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
fuente
2

OK, si su objetivo es averiguar qué puede arreglar para hacerlo más rápido, ese es un objetivo un poco diferente. Medir el tiempo que tardan las funciones es una buena manera de averiguar si lo que hiciste marcó la diferencia, pero para saber qué hacer necesitas una técnica diferente. Esto es lo que recomiendo , y sé que puedes hacerlo en iPhones.

Editar: los revisores me sugirieron que elaborara la respuesta, así que estoy tratando de pensar en una breve forma de decirla.
Su programa general toma suficiente tiempo de reloj para molestarlo. Supongamos que son N segundos.
Estás asumiendo que puedes acelerarlo. La única forma de hacerlo es haciendo que no haga algo que está haciendo en ese tiempo, representando m segundos.
Inicialmente no sabes qué es esa cosa. Puedes adivinar, como lo hacen todos los programadores, pero fácilmente podría ser otra cosa. Sea lo que sea, así es como puedes encontrarlo:

Dado que esa cosa, sea lo que sea, representa la fracción m / N del tiempo, eso significa que si la pausas al azar, la probabilidad es m / N de que la atraparás en el acto de hacer esa cosa. Por supuesto, podría estar haciendo otra cosa, pero pause y vea qué está haciendo.
Ahora hazlo de nuevo. Si lo ves haciendo lo mismo otra vez, puedes sospechar más.

Hazlo 10 veces, o 20. Ahora, si ves que hace algo en particular (no importa cómo lo describas) en múltiples pausas, de las que puedes deshacerte, sabes dos cosas. Sabes más o menos qué fracción de tiempo lleva, pero sabes exactamente qué arreglar.
Si también desea saber exactamente cuánto tiempo se ahorrará, eso es fácil. Mídelo antes, arréglalo y mídelo después. Si realmente estás decepcionado, retira la solución.

¿Ves cómo esto es diferente de medir? Es encontrar, no medir . La mayoría de los perfiles se basan en medir de la manera más exacta posible cuánto tiempo se toma, como si eso fuera importante, y agita con la mano el problema de identificar lo que necesita ser arreglado. La creación de perfiles no encuentra todos los problemas, pero este método sí encuentra todos los problemas, y son los problemas que no encuentra los que lo lastiman.

Mike Dunlavey
fuente
0

Aquí hay otra forma, en Swift, de hacerlo usando la palabra clave de aplazamiento

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

De los documentos de Apple : se usa una declaración de aplazamiento para ejecutar código justo antes de transferir el control del programa fuera del alcance en el que aparece la declaración de aplazamiento.

Esto es similar a un bloque try / finally con la ventaja de tener el código relacionado agrupado.

CMont
fuente
0

Lo uso en mi biblioteca de utilidades ( 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.")
    }
}

... luego llame a un método como:

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

... que a su vez se ve así en la consola después de ejecutar:

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

No es tan conciso como TICK / TOCK anterior, pero es lo suficientemente claro como para ver lo que está haciendo e incluye automáticamente lo que se está cronometrando (por archivo, línea al inicio del método y nombre de la función). Obviamente, si quisiera más detalles (por ejemplo, si no solo estoy cronometrando una llamada al método como es el caso habitual, sino que estoy cronometrando un bloque dentro de ese método), puedo agregar el parámetro "name =" Foo "" en el init PrintTimer por nombrarlo además de los valores predeterminados.

Tom Dibble
fuente
-1

Dado que desea optimizar el tiempo de pasar de una página a otra en un UIWebView, ¿no significa que realmente está buscando optimizar el Javascript utilizado para cargar estas páginas?

Con ese fin, miraría un perfilador de WebKit como el que se menciona aquí:

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

Otro enfoque sería comenzar a un alto nivel y pensar cómo puede diseñar las páginas web en cuestión para minimizar los tiempos de carga utilizando la carga de páginas de estilo AJAX en lugar de actualizar toda la vista web cada vez.

Kendall Helmstetter Gelner
fuente
-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)
}
usuario3441734
fuente
-1

Aquí hay una solución Swift 3 para dividir el código en cualquier lugar para encontrar un proceso de larga ejecución.

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

Salida de muestra: -

TIEMPOS TRANSCURRIDOS [MyApp.SomeViewController.Instrumentation (título: "Start View Did Load", punto: 1, elapsedTime: 0.040504038333892822), MyApp.SomeViewController.Instrumentation (título: "Finished Adding SubViews", punto: 2, elapsedTime: 0.0105850100517, MyApp.SomeViewController.Instrumentation (título: "La vista apareció", punto: 3, tiempo transcurrido: 0.56564098596572876)]

Markhorrocks
fuente
-1

muchas respuestas son extrañas y realmente no dan resultado en milisegundos (pero en segundos o cualquier otra cosa):

Aquí lo que uso para obtener MS (MILLISECONDS):

Rápido:

let startTime = NSDate().timeIntervalSince1970 * 1000

// your Swift code

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

C objetivo:

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 );
usuario924
fuente
-1

Para Swift 4, agregue como delegado a su clase:

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

Añadir a nuestra clase:

class MyViewcontroller: UIViewController, TimingDelegate

Luego agregue a su clase:

var _TICK: Date?

Cuando quieras cronometrar algo, comienza con:

TICK

Y termina con:

TOCK("Timing the XXX routine")
Snickitybit
fuente
¿Leíste las respuestas y los comentarios? ¡No use la fecha para esto!
mate