Как записать время выполнения метода точно в миллисекундах?


222

Есть ли способ определить, сколько времени необходимо выполнить методу (в миллисекундах)?


2
Вы случайно спрашиваете, потому что хотите узнать, что можно оптимизировать, чтобы сделать это быстрее?
Майк Данлавей

1
Да, я использую UIWebView, который загружает некоторые страницы. Я хочу , чтобы оптимизировать pageloading путем проверки времени метод должен загрузить страницу 1 на странице 10.
дан

2
Похоже, это дубликат этого вопроса: stackoverflow.com/questions/889380/…
Брэд Ларсон

@BradLarson Несмотря на то, что он выглядит дубликатом, другой вопрос имеет лучшие ответы, то есть там выдающиеся ответы не предлагают использовать (неправильный) NSDate, но вместо этого хорошо объясняет, почему NSDate - неправильный способ сделать это для этой цели.
Томас Темпельманн

Ответы:


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

Прост в использовании и имеет точность до миллисекунды.


3
@PeterWarbo NSTimeInterval является ЬурейиМ двойным и определяются как количество секунд - см developer.apple.com/library/mac/#documentation/Cocoa/Reference/...
Бен Лингс

5
Вы можете записать это значение с помощью% f - NSLog ("executeTime =% f", executeTime);
Тони

1
@ Тони, ты забыл @,NSLog(@"executionTime = %f", executionTime);
Джон Риселвато

6
Я только что сравнил NSDateи mach_absolute_time()на уровне около 30 мс. 27 против 29, 36 против 39, 43 против 45. NSDateМне было проще пользоваться, и результаты были достаточно схожими, чтобы не беспокоиться mach_absolute_time().
Неванский король

5
Все, что основано на NSDate, небезопасно для измерения прошедшего времени, потому что время может прыгать, даже назад. Гораздо более безопасный способ - использовать mach_absolute_time, как показано во многих других ответах здесь. Этот должен быть опущен за плохой пример. См. Также соответствующий ответ, который объясняет все это более подробно: stackoverflow.com/a/30363702/43615
Томас Темпельманн

252

Вот два однострочных макроса, которые я использую:

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

Используйте это так:

TICK;

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

TOCK;

13
Ха - ха. Мне это нравится!
bobmoff

5
То, что делает это настолько хорошим, - то, что тик-так - такая запоминающаяся фраза, что регистрация почти не требует мысли.
Джон Риселвато,

30
#define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow])заставляет этот ответ также возвращать, в какой функции использовался таймер. Я нашел это полезным, если использовал TICK TOCK для измерения времени нескольких функций.
golmschenk

3
Отличная идея @golmschenk! Вы также можете посмотреть __PRETTY_FUNCTION__и, __LINE__если вы хотите более подробную информацию.
Рон

50

Для детальной синхронизации в OS X вы должны использовать mach_absolute_time( )объявленный в <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;

Конечно, применяются обычные предостережения о мелкозернистых измерениях; вам, вероятно, лучше всего многократно вызывать тестируемую процедуру и усреднять / брать минимум / какую-то другую форму обработки.

Кроме того, обратите внимание, что может оказаться более полезным профилировать приложение, запущенное с помощью такого инструмента, как Shark. Это не даст вам точную информацию о времени, но скажет, какой процент времени приложения тратится, где, что часто более полезно (но не всегда).


1
Пытаетесь заставить это работать в Свифте ... есть предложения?
zumzum

1
«Нельзя просто ... преобразовать в Свифта» - Нед Старк
stonedauwg

@zumzum Смотрите мой ответ для примера, как это сделать в Swift.
JBG

23

Есть удобная обёртка для mach_absolute_time()- это CACurrentMediaTime()функция.

В отличие от NSDateили CFAbsoluteTimeGetCurrent()смещений, mach_absolute_time()и CACurrentMediaTime()основаны на внутренних часах хоста, точном одноатомном измерении и не подвержены изменениям внешнего эталона времени, например, вызванного часовыми поясами, переходом на летнее время или дополнительными секундами.


ObjC

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

стриж

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

3
Я думаю, что этот ответ заслуживает большего количества голосов. Это намного лучше, чем использовать NSDate.
средний Джо

12

В Swift я использую:

В моем Macros.swift я только что добавил

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

теперь вы можете просто позвонить куда угодно

TICK()

// your code to be tracked

TOCK()
  • этот код основан на переводе кода Рона на Swift, у него есть кредиты
  • Я использую дату начала на глобальном уровне, любые предложения по улучшению приветствуются

Это должно быть \(-startTime.timeIntervalSinceNow)(обратите внимание на негатив)
Снеговик

9

Я знаю, что это старый, но даже я обнаружил, что снова прошёл мимо него, поэтому я решил представить свой вариант здесь.

Лучше всего проверить мой пост на этом блоге: Время в Objective-C: секундомер

По сути, я написал класс, который перестает смотреть в основном виде, но инкапсулирован так, что вам нужно только сделать следующее:

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

И вы в конечном итоге:

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

в журнале ...

Снова, проверьте мой пост для немного больше или загрузите его здесь: MMStopwatch.zip


7

Я использую макросы на основе решения Рона .

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

Для строк кода:

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

мы увидим в консоли что-то вроде: TIME1: 0.096618


Ваш ответ на самом деле не сильно отличается от ответа Рона, а также я почему-то не понимаю, как он лучше?
Триларион

2
Вы не можете использовать решение @ Рона в одном контексте дважды. Это главная причина для этого макроса.
Сергей Терехин

4

Я использую очень минимальную, одностраничную реализацию класса, вдохновленную кодом из этого поста :

#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

Использование этого очень просто:

  • просто позвоните [DBGStopwatch start:@"slow-operation"];в начале
  • а затем [DBGStopwatch stop:@"slow-operation"];после финиша, чтобы получить время

3

С помощью этого класса StopWatch вы можете получить очень хорошее время (секунды.части секунд). Он использует высокоточный таймер в iPhone. Использование NSDate только даст вам точность (секунды). Эта версия разработана специально для autorelease и target-c. У меня есть версия C ++, а также при необходимости. Вы можете найти версию C ++ здесь .

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

У класса есть статический stopWatchметод, который возвращает автоматически выпущенный объект.

Как только вы позвоните start, используйте secondsметод, чтобы получить истекшее время. Звоните startснова, чтобы перезапустить его. Или stopостановить это. Вы все еще можете прочитать время (звонок seconds) в любое время после звонка stop.

Пример в функции (время выполнения вызова)

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

   ... do stuff

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

Ваша «точность только секунд» неверна. В то время как вся часть NSTimeInterval составляет секунды, это двойная величина.
Стивен Фишер

3

Я использую этот код:

#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

Я использую это:

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

Но я не уверен насчет CLOCKS_PER_SEC на iPhone. Вы можете оставить это.


2
CLOCKS_PER_SEC на iPhone - крайне неточное значение.
mxcl

1
Хорошо знать. Я бы использовал ответ Мэтью, если бы мне пришлось сделать это сейчас.
Дэвид Канарек

2

Пример точной синхронизации с использованием 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")
}

2

Хорошо, если ваша цель - выяснить, что вы можете исправить, чтобы сделать это быстрее, это немного другая цель. Измерение времени, которое занимают функции, - хороший способ выяснить, изменилось ли то, что вы сделали, но чтобы узнать, что делать, вам нужна другая техника. Это то, что я рекомендую , и я знаю, что вы можете сделать это на iPhone.

Изменить: рецензенты предложили мне разработать ответ, поэтому я пытаюсь придумать краткий способ сказать это.
Ваша общая программа занимает достаточно времени, чтобы беспокоить вас. Предположим, это N секунд.
Вы предполагаете, что можете ускорить это. Единственный способ сделать это - заставить его не делать то, что он делает за это время, что составляет m секунд.
Вы изначально не знаете, что это за вещь. Вы можете догадаться, как и все программисты, но это может быть что-то другое. Что бы это ни было, вот как это можно найти:

Поскольку эта вещь, какой бы она ни была, учитывает долю m / N времени, это означает, что если вы остановите ее наугад, вероятность того, что вы это поймете, будет равна m / N. Конечно, он может делать что-то еще, но сделайте паузу и посмотрите, что он делает.
Теперь сделай это снова. Если вы видите, что он делает то же самое снова, вы можете быть более подозрительным.

Сделайте это 10 раз или 20. Теперь, если вы видите, что он делает какую-то конкретную вещь (независимо от того, как вы это описываете) в нескольких паузах, от которой вы можете избавиться, вы знаете две вещи. Вы очень точно знаете, сколько времени это занимает, но вы точно знаете , что нужно исправить.
Если вы также хотите точно знать , сколько времени будет сэкономлено, это легко. Измерьте это прежде, исправьте это, и измерьте это после. Если вы действительно разочарованы, отмените исправление.

Вы видите, как это отличается от измерения? Это нахождение, а не измерение . Большая часть профилирования основана на измерении как можно точнее того, сколько времени уходит, как будто это важно, и решает проблему определения того, что необходимо исправить. Профилирование не находит всех проблем, но этот метод находит все проблемы, и проблемы, которые вы не находите, причиняют вам боль.


0

Вот другой способ, в Swift, сделать это с помощью ключевого слова defer

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

Из документов Apple : оператор defer используется для выполнения кода непосредственно перед передачей управления программой за пределы области действия, в которой появляется оператор defer.

Это похоже на блок try / finally с преимуществом группирования соответствующего кода.


0

Я использую это в моей библиотеке утилит ( 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.")
    }
}

... затем вызовите метод, как:

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

... который в свою очередь выглядит в консоли после запуска:

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

Не так кратко, как TICK / TOCK выше, но достаточно ясно, чтобы увидеть, что он делает, и автоматически включает в себя то, что рассчитывается (по файлу, строке в начале метода и имени функции). Очевидно, что если бы я хотел больше подробностей (например, если я не просто синхронизирую вызов метода, как это обычно бывает, а вместо этого синхронизирую блок внутри этого метода), я могу добавить параметр "name =" Foo "" в инициализации PrintTimer назвать это что-то помимо значений по умолчанию.


-1

Поскольку вы хотите оптимизировать время перехода с одной страницы на другую в UIWebView, не значит ли это, что вы действительно хотите оптимизировать Javascript, используемый при загрузке этих страниц?

Для этого я бы посмотрел на профилировщик WebKit, о котором говорилось здесь:

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

Другой подход заключается в том, чтобы начать с высокого уровня и подумать, как можно спроектировать соответствующие веб-страницы, чтобы минимизировать время загрузки, используя загрузку страниц в стиле AJAX вместо того, чтобы каждый раз обновлять весь веб-просмотр.


-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

Вот решение Swift 3 для разделения кода в любом месте, чтобы найти длительный процесс.

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

Использование: -

instrument("View Did Appear")

print("ELAPSED TIMES \(elapsedTimes)")

Пример вывода: -

РАСШИРЕННЫЕ ВРЕМЕНИ [MyApp.SomeViewController.Instrumentation (title: «Начальный просмотр загружен», точка: 1, elapsedTime: 0.040504038333892822), MyApp.SomeViewController.Instrumentation (title: «Закончено добавление SubViews», точка: 2, elapsedTime: 0,015 005 025), 0,015: 0 5015 052 0: 0 0 0 0 0 0 0 0 0 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 005 0, 005 05 0 5 0 5 0 5 0 5 5 5 5 5 5 5 5 5 5 5 5 5 на время нахождения в у.и. MyApp.SomeViewController.Instrumentation (заголовок: «Представление появилось», точка: 3, прошедшее время: 0,56564098596572876)]


-1

многие ответы странные и на самом деле не дают результата в миллисекундах (но в секундах или чем-то еще):

вот что я использую, чтобы получить MS (миллисекунды):

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

Для Swift 4 добавьте делегата в свой класс:

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

Добавьте к нашему классу:

class MyViewcontroller: UIViewController, TimingDelegate

Затем добавьте в свой класс:

var _TICK: Date?

Когда вы хотите рассчитать время, начните с:

TICK

И конец:

TOCK("Timing the XXX routine")

Вы читали ответы и комментарии? Не используйте дату для этого!
матовый
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.