【问题标题】:How to log a method's execution time exactly in milliseconds?如何以毫秒为单位准确记录方法的执行时间?
【发布时间】:2011-01-08 22:39:17
【问题描述】:

有没有办法确定一个方法需要执行多长时间(以毫秒为单位)?

【问题讨论】:

  • 您是否有机会询问,因为您想了解可以优化哪些内容以使其更快?
  • 是的,我正在使用正在加载一些页面的 UIWebView。我想通过检查方法需要将第 1 页加载到第 10 页的时间来优化页面加载。
  • 这似乎是这个问题的重复:stackoverflow.com/questions/889380/…
  • @BradLarson 虽然它似乎是重复的,但另一个问题有更好的答案,即突出的答案并不建议使用(不正确的)NSDate,而是很好地解释了为什么 NSDate 是错误的为此目的的方法。

标签: ios objective-c optimization time


【解决方案1】:

好的,如果您的目标是找出可以解决的问题以使其更快,那么目标就有些不同了。测量函数所花费的时间是了解您所做的事情是否有所作为的好方法,但是要找出要做什么,您需要一种不同的技术。 This is what I recommend,我知道你可以在 iPhone 上做到这一点。

编辑:审稿人建议我详细说明答案,所以我试图想一个简短的方式来表达它。
您的整个程序需要足够的时钟时间来打扰您。假设那是 N 秒。
你假设你可以加快速度。你可以做到这一点的唯一方法是让它在那个时间里不做它正在做的事情,占 m 秒。
你一开始并不知道那是什么东西。你可以猜到,就像所有程序员一样,但它很容易是别的东西。不管是什么,您都可以通过以下方式找到它:

因为那件事,不管它是什么,占时间的一小部分 m/N,这意味着如果你随机暂停它,概率是 m/N你会在做那件事的过程中抓住它。当然,它可能正在做其他事情,但暂停一下,看看它在做什么。
现在再做一次。如果您再次看到它做同样的事情,您可能会更加怀疑。

做 10 次,或 20 次。现在,如果你看到它在多次停顿时做某件特定的事情(不管你如何描述它),你可以摆脱它,你知道两件事。您非常粗略地知道需要多少时间,但您非常准确地知道要解决什么问题。
如果您还想非常准确地了解将节省多少时间,这很容易。先测量,修好,再测量。如果您真的很失望,请退出修复。

您知道这与测量有何不同吗?这是发现,而不是测量。大多数分析都是基于尽可能准确地测量花费了多少时间,就好像这很重要一样,并且解决了确定需要修复的问题的问题。剖析不会发现所有问题,但这种方法确实会发现所有问题,而伤害你的是你没有发现的问题。

【讨论】:

    【解决方案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);
    

    但我不确定 iPhone 上的 CLOCKS_PER_SEC。你可能想把它关掉。

    【讨论】:

    • CLOCKS_PER_SEC 在 iPhone 上是一个非常不准确的值。
    • 很高兴知道。如果我现在必须这样做,我会使用马修的答案。
    • 在 Mac 上试过 - 远非准确
    【解决方案3】:

    对于 OS X 上的细粒度计时,您应该使用在 <mach/mach_time.h> 中声明的 mach_absolute_time( )

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

    当然,关于细粒度测量的常见警告也适用;您可能最好多次调用被测例程,然后平均/取最小值/其他形式的处理。

    此外,请注意,profile 您可能会发现使用 Shark 等工具运行的应用程序更有用。这不会为您提供准确的时间信息,但会告诉您应用程序的时间在哪里花费了百分比,这通常更有用(但并非总是如此)。

    【讨论】:

    • 试图让它在 Swift 中工作......有什么建议吗?
    • “一个人不会简单地......转换为 Swift” - Ned Stark
    • @zumzum 查看我的答案,了解在 Swift 中执行此操作的示例。
    【解决方案4】:
    NSDate *methodStart = [NSDate date];
    
    /* ... Do whatever you need to do ... */
    
    NSDate *methodFinish = [NSDate date];
    NSTimeInterval executionTime = [methodFinish timeIntervalSinceDate:methodStart];
    NSLog(@"executionTime = %f", executionTime);
    

    斯威夫特:

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

    易于使用且具有亚毫秒级精度。

    【讨论】:

    • @PeterWarbo NSTimeInterval 是 double 的 typedef,定义为秒 - 请参阅 developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
    • 你可以用 %f 记录这个值 - NSLog("executionTime = %f", executionTime);
    • @Tony 你忘了@,NSLog(@"executionTime = %f", executionTime);
    • 我刚刚比较了 NSDatemach_absolute_time() 在大约 30 毫秒的水平。 27 vs. 29, 36 vs. 39, 43 vs. 45。NSDate 对我来说更容易使用,结果非常相似,不用打扰mach_absolute_time()
    • 基于 NSDate 的任何东西对于测量经过的时间都是不安全的,因为时间可以跳跃,甚至倒退。一种更安全的方法是使用 mach_absolute_time,如此处的许多其他答案所示。这个应该被否决,因为它是一个坏例子。另请参阅更详细地解释这一切的相关答案:stackoverflow.com/a/30363702/43615
    【解决方案5】:

    既然您想优化在 UIWebView 中从一个页面移动到另一个页面的时间,这是否意味着您真的希望优化加载这些页面时使用的 Javascript?

    为此,我会看看这里谈到的 WebKit 分析器:

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

    另一种方法是从高层次开始,思考如何设计相关网页,以使用 AJAX 样式的页面加载来减少加载时间,而不是每次都刷新整个 web 视图。

    【讨论】:

      【解决方案6】:

      我知道这是一个旧的,但即使我发现自己再次徘徊,所以我想我会在这里提交我自己的选择。

      最好的办法是查看我的博客文章: Timing things in Objective-C: A stopwatch

      基本上,我写了一个类,它确实以非常基本的方式停止观看,但被封装,因此您只需要执行以下操作:

      [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   NSDate *startTime = [NSDate date]
        #define TOCK   NSLog(@"Time: %f", -[startTime timeIntervalSinceNow])
        

        像这样使用它:

        TICK;
        
        /* ... Do Some Work Here ... */
        
        TOCK;
        

        【讨论】:

        • 之所以如此出色,是因为 tick-tock 是一个令人难忘的短语,几乎不需要思考。
        • #define TOCK NSLog(@"%s Time: %f", __func__, -[startTime timeIntervalSinceNow]) 使这个答案也返回计时器用于哪个功能。如果我使用 TICK TOCK 来计时多个功能,我发现这很有用。
        • 好主意@golmschenk!如果您想了解更多详细信息,还可以查看__PRETTY_FUNCTION____LINE__
        • Tick/Tock 很酷,但最好不要使用 NSDate 来准确计时。
        【解决方案8】:

        您可以使用此 StopWatch 类真正获得精确的计时(seconds.parts of seconds)。它使用 iPhone 中的高精度计时器。使用 NSDate 只会让您获得秒级的准确性。这个版本是专门为 autorelease 和 objective-c 设计的。如果需要,我也有一个 c++ 版本。 You can find the c++ version here.

        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 停止它。拨打stop后,您仍然可以随时读取时间(拨打seconds)。

        函数中的示例(执行时间调用)

        -(void)SomeFunc
        {
           StopWatch* stopWatch = [StopWatch stopWatch];
           [stopWatch Start];
        
           ... do stuff
        
           [stopWatch StopWithContext:[NSString stringWithFormat:@"Created %d Records",[records count]]];
        }
        

        【讨论】:

        • 您的“仅秒精度”不正确。虽然 NSTimeInterval 的整个部分是秒,但它是双倍的。
        【解决方案9】:

        我使用受code from this blog post启发的极简单页类实现:

        #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"];,获取时间

        【讨论】:

          【解决方案10】:

          在 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 5.0

             var startTime = NSDate()
          func TICK(){ startTime =  NSDate() }
          func TOCK(function: String = #function, file: String = #file, line: Int = #line){
              print("\(function) Time: \(startTime.timeIntervalSinceNow)\nLine:\(line) File: \(file)")
          }
          
          • 此代码基于 Ron 的代码翻译成 Swift,他有学分
          • 我在全球范围内使用开始日期,欢迎提出任何改进建议

          【讨论】:

          • 这应该是\(-startTime.timeIntervalSinceNow)(注意是否定的)
          【解决方案11】:

          我使用这个代码:

          #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;
          }
          

          【讨论】:

            【解决方案12】:
            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)
            }
            

            【讨论】:

              【解决方案13】:

              我使用基于Ron's 解决方案的宏。

              #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

              【讨论】:

              • 您的回答与 Ron 的回答并没有太大的不同,而且我不知何故看不出哪种方式更好?
              • 您不能在一个上下文中使用@Ron 的解决方案两次。这是这个宏的主要原因。
              【解决方案14】:

              这是在 Swift 中使用 defer 关键字的另一种方式

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

              来自 Apple 的 docsdefer 语句用于在将程序控制转移到 defer 语句出现的范围之外之前执行代码。

              这类似于 try/finally 块,优点是将相关代码分组。

              【讨论】:

                【解决方案15】:

                这是一个 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,经过时间:0.040504038333892822), MyApp.SomeViewController.Instrumentation(title: "添加完毕 子视图”,点:2,经过时间:0.010585010051727295), MyApp.SomeViewController.Instrumentation(title: "View Did Appear", 点:3,经过时间:0.56564098596572876)]

                【讨论】:

                  【解决方案16】:

                  许多答案很奇怪,并没有真正以毫秒为单位给出结果(但以秒或其他任何时间为单位):

                  这是我用来获得 MS (MILLISECONDS) 的东西:

                  斯威夫特:

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

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

                  【讨论】:

                    【解决方案17】:

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

                    【讨论】:

                    • 你读过答案和cmets吗?不要为此使用日期!
                    【解决方案18】:

                    我在我的 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.")
                        }
                    }
                    

                    ...然后调用类似的方法:

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

                    ...运行后在控制台中又是这样的:

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

                    不像上面的 TICK/TOCK 那样简洁,但足够清楚地看到它在做什么,并自动包含正在计时的内容(按文件、方法开头的行和函数名称)。显然,如果我想要更多细节(例如,如果我不只是像通常情况那样对方法调用进行计时,而是在该方法中计时一个块),我可以在 PrintTimer init 上添加“name="Foo"”参数将其命名为默认值之外的其他名称。

                    【讨论】:

                      【解决方案19】:

                      在 Swift 4 中使用 mach_absolute_time() 的细粒度计时示例:

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

                      【讨论】:

                        【解决方案20】:

                        mach_absolute_time() 有一个方便的包装器——它是一个 CACurrentMediaTime() 函数。

                        不同于NSDateCFAbsoluteTimeGetCurrent() 偏移量, 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")
                        

                        【讨论】:

                        • 我认为这个答案值得更多的支持。这比使用NSDate 好多了。
                        • 这和CFAbsoluteTimeGetCurrent()一样吗?改名了吗?
                        • @EzekielElin – 不,CFAbsoluteTimeGetCurrent 根据 Apple 文档,“可能由于与外部时间参考同步或由于用户明确更改时钟而减少”,因此它不适合基准测试
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-11-24
                        • 1970-01-01
                        • 2022-11-27
                        • 2022-06-28
                        • 1970-01-01
                        相关资源
                        最近更新 更多