在Swift中测量经过时间


132

我们如何衡量在Swift中运行一个函数所花费的时间?我试图以这种方式显示经过的时间:“经过的时间为.05秒”。看到在Java中,我们可以使用System.nanoTime(),Swift中有可用的等效方法来实现这一点吗?

请看一下示例程序:

func isPrime(var number:Int) ->Bool {
    var i = 0;
    for i=2; i<number; i++ {
        if(number % i == 0 && i != 0) {
            return false;
        }
    }
    return true;
}

var number = 5915587277;

if(isPrime(number)) {
    println("Prime number");
} else {
    println("NOT a prime number");
}

1
它与您的时间测量问题无关,但是可以在sqrt(number)而不是在处停止循环number,您可以节省更多时间-但是有更多的想法可以优化求素数的过程。
Holex 2014年

@holex请忽略使用的算法。我试图弄清楚如何测量经过的时间?
Vaquita 2014年

1
您可以使用NSDate对象,也可以测量它们之间的差异。
Holex 2014年

4
如果您使用的是XCode,建议您使用新的性能测试功能。它为您完成了所有繁重的工作,甚至可以多次运行,并为您提供平均时间及其标准偏差...
Roshan 2014年

1
@dumbledad您可以直接在单元测试中测量整个块的性能。例如,参见this。如果您想进一步细分运行(例如逐行代码),请查看仪器的一部分Time Profiler。这更加强大和全面。
Roshan 2015年

Answers:


228

这是我编写的用于测量Swift中的Euler问题的Swift函数

从Swift 3开始,Grand Central Dispatch现在有一个“精简版”版本。因此正确的答案可能是使用DispatchTime API

我的函数看起来像:

// Swift 3
func evaluateProblem(problemNumber: Int, problemBlock: () -> Int) -> Answer
{
    print("Evaluating problem \(problemNumber)")

    let start = DispatchTime.now() // <<<<<<<<<< Start time
    let myGuess = problemBlock()
    let end = DispatchTime.now()   // <<<<<<<<<<   end time

    let theAnswer = self.checkAnswer(answerNum: "\(problemNumber)", guess: myGuess)

    let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // <<<<< Difference in nano seconds (UInt64)
    let timeInterval = Double(nanoTime) / 1_000_000_000 // Technically could overflow for long running tests

    print("Time to evaluate problem \(problemNumber): \(timeInterval) seconds")
    return theAnswer
}

旧答案

对于Swift 1和2,我的函数使用NSDate:

// Swift 1
func evaluateProblem(problemNumber: Int, problemBlock: () -> Int) -> Answer
{
    println("Evaluating problem \(problemNumber)")

    let start = NSDate() // <<<<<<<<<< Start time
    let myGuess = problemBlock()
    let end = NSDate()   // <<<<<<<<<<   end time

    let theAnswer = self.checkAnswer(answerNum: "\(problemNumber)", guess: myGuess)

    let timeInterval: Double = end.timeIntervalSinceDate(start) // <<<<< Difference in seconds (double)

    println("Time to evaluate problem \(problemNumber): \(timeInterval) seconds")
    return theAnswer
}

请注意,不建议将NSdate用于计时功能:“ 由于与外部时间参考同步或由于用户明确更改了时钟,因此系统时间可能会减少。 ”。


这似乎可以使结果达到大约+/- 2微秒。但是,可能会因平台而异。
user1021430 2014年

6
swift3对我不起作用,它返回0.0114653027,我知道这是不正确的,带有日期的返回4.77720803022385sec
Cristi Băluță 16'Nov

5
不幸的是没有。由于某种原因,正常运行时间已完全耗尽。我有一个测试,显示耗时1004959766纳秒(约一秒),但运行了大约40秒钟。我Date与之并肩使用,并且确实可以报告41.87秒。我不知道uptimeNanoseconds在做什么,但是没有报告正确的持续时间。
jowie '16

2
抱歉,进行较大的编辑会优先考虑较新的解决方案,但我什至建议您完全删除您的NSDate代码:使用NSDate完全不可靠:NSDate基于系统时钟,由于许多不同的原因,它可能随时更改,例如随着网络时间同步(NTP)更新时钟(通常会针对漂移进行调整),DST调整,leap秒和用户手动调整时钟。此外,您无法再使用Swift 1在AppStore上发布任何应用程序:Swift 3是最低要求。因此,除了说“不做”之外,别无保留NSDate代码。
心教堂

1
@Cur您对NSDate版本的批评是正确的(除非DST更改不会影响GSD中始终存在的NSDate),并且您对顺序进行反向编辑绝对是一种改进。
JeremyP

69

这是一个基于CoreFoundations 的便捷计时器类CFAbsoluteTime

import CoreFoundation

class ParkBenchTimer {

    let startTime:CFAbsoluteTime
    var endTime:CFAbsoluteTime?

    init() {
        startTime = CFAbsoluteTimeGetCurrent()
    }

    func stop() -> CFAbsoluteTime {
        endTime = CFAbsoluteTimeGetCurrent()

        return duration!
    }

    var duration:CFAbsoluteTime? {
        if let endTime = endTime {
            return endTime - startTime
        } else {
            return nil
        }
    }
}

您可以像这样使用它:

let timer = ParkBenchTimer()

// ... a long runnig task ...

println("The task took \(timer.stop()) seconds.")

4
这个功能千呼万唤不保证单调递增的结果。 ”根据其文档
富兰克林·于

8
更多上下文:“对该函数的重复调用不能保证结果单调增加。由于与外部时间参考同步或由于用户明确更改了时钟,因此系统时间可能会减少。”
克拉斯

开发人员应考虑“与外部时间参考的同步”和“用户的显式时钟更改”。您是说这两种情况不可能发生吗?
富兰克林·于

@FranklinYu不,我只是想说明一下这是两个可能的原因。如果您始终依赖于单调增加值,那么还有其他选择。
克拉斯

有时我想测量一段短时间;如果在此期间发生同步,我可能会得到负面的时间。实际上mach_absolute_time,没有哪一个不受此问题困扰,所以我想知道为什么那个人并没有广为人知。可能只是因为没有充分记录。
富兰克林·于

54

使用clockProcessInfo.systemUptimeDispatchTime设置简单的启动时间。


据我所知,至少有十种方法来测量经过时间:

基于单调时钟:

  1. ProcessInfo.systemUptime
  2. mach_absolute_timemach_timebase_info在提到这个答案
  3. clock()POSIX标准中
  4. times()POSIX标准中。(太复杂了,因为我们需要考虑用户时间与系统时间,并且涉及子进程。)
  5. DispatchTime (围绕Mach time API的包装),如JeremyP在公认的答案中提到的那样。
  6. CACurrentMediaTime()

基于挂钟:

(请勿将其用于指标:请参阅下面的原因)

  1. NSDate/ Date正如其他人所提到的。
  2. CFAbsoluteTime 正如别人提到的。
  3. DispatchWallTime
  4. gettimeofday()POSIX标准中

选项1、2和3如下所述。

选项1:Foundation中的Process Info API

do {
    let info = ProcessInfo.processInfo
    let begin = info.systemUptime
    // do something
    let diff = (info.systemUptime - begin)
}

diff:NSTimeInterval经过的时间在哪里,以秒为单位。

选项2:Mach C API

do {
    var info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info)
    let begin = mach_absolute_time()
    // do something
    let diff = Double(mach_absolute_time() - begin) * Double(info.numer) / Double(info.denom)
}

其中diff:Double是通过毫微秒的经过时间。

选项3:POSIX时钟API

do {
    let begin = clock()
    // do something
    let diff = Double(clock() - begin) / Double(CLOCKS_PER_SEC)
}

diff:Double经过的时间在哪里,以秒为单位。

为什么不使用挂钟时间作为经过时间?

在以下文档中CFAbsoluteTimeGetCurrent

重复调用此函数不能保证单调递增的结果。

原因类似于Java中的currentTimeMillisvsnanoTime

您不能将一个用于其他目的。原因是没有计算机的时钟是完美的。它总是漂移,偶尔需要校正。这种校正可能是手动进行的,或者对于大多数机器而言,有一个进程在运行,并不断对系统时钟(“挂钟”)进行小的校正。这些往往经常发生。每当有leap秒时,就会发生另一种此类校正。

这里CFAbsoluteTime提供了挂钟时间而不是启动时间。NSDate也是挂钟时间。


最佳答案-但是,什么是最好的方法?
Fattie

1
选项5最适合我。如果您考虑使用多平台代码,请尝试使用此代码。Darwin和Glibc库均具有clock()功能。
Misha Svyatoshenko

对于基于系统时钟解决方案:我有成功的ProcessInfo.processInfo.systemUptimemach_absolute_time()DispatchTime.now()CACurrentMediaTime()。但解决方案clock()无法正确转换为秒。所以,我建议更新您的第一句话:“ 使用ProcessInfo.systemUptime或DispatchTime准确的启动时间。
心教堂

36

Swift 4最短答案:

let startingPoint = Date()
//  ... intensive task
print("\(startingPoint.timeIntervalSinceNow * -1) seconds elapsed")

它将打印出大约1.02107906341553秒的时间(当然,时间会因任务而异,我只是向大家展示,以查看此测量的小数精度水平)。

希望它可以从现在开始帮助Swift 4中的某个人!

更新资料

如果您想使用一种通用的方法来测试部分代码,建议您使用下一个代码段:

func measureTime(for closure: @autoclosure () -> Any) {
    let start = CFAbsoluteTimeGetCurrent()
    closure()
    let diff = CFAbsoluteTimeGetCurrent() - start
    print("Took \(diff) seconds")
}

*Usage*

measureTime(for: <insert method signature here>)

**Console log**
Took xx.xxxxx seconds

有人可以解释为什么* -1吗?
Maksim Kniazev '18年

1
@MaksimKniazev因为它是负值,所以人们想要正值!
thachnb

@thachnb我不知道“ startingPoint.timeIntervalSinceNow”会产生负值...
Maksim

1
@MaksimKniazev苹果表示:如果日期对象早于当前日期和时间,则此属性的值为负。
彼得·关

13
let start = NSDate()

for index in 1...10000 {
    // do nothing
}

let elapsed = start.timeIntervalSinceNow

// elapsed is a negative value.

2
abs(start.timeIntervalSinceNow)// <-正值
eonist

几乎是我见过的最直接的解决方案,谢谢
MiladiuM

10

只需复制并粘贴此功能。快速撰写。5.在此处复制JeremyP。

func calculateTime(block : (() -> Void)) {
        let start = DispatchTime.now()
        block()
        let end = DispatchTime.now()
        let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
        let timeInterval = Double(nanoTime) / 1_000_000_000
        print("Time: \(timeInterval) seconds")
    }

像这样使用

calculateTime {
     exampleFunc()// function whose execution time to be calculated
}

6

您可以创建一个time用于测量通话的函数。我受到克拉斯回答的启发。

func time <A> (f: @autoclosure () -> A) -> (result:A, duration: String) {
    let startTime = CFAbsoluteTimeGetCurrent()
    let result = f()
    let endTime = CFAbsoluteTimeGetCurrent()
    return (result, "Elapsed time is \(endTime - startTime) seconds.")
}

该函数将允许您这样调用它time (isPrime(7)),它将返回一个元组,其中包含结果和经过时间的字符串描述。

如果您只希望经过的时间,可以这样做 time (isPrime(7)).duration


3
“重复调用此函数不能保证单调增加结果。” 根据文件
富兰克林·于

3

简单的辅助函数,用于测量关闭时的执行时间。

func printExecutionTime(withTag tag: String, of closure: () -> ()) {
    let start = CACurrentMediaTime()
    closure()
    print("#\(tag) - execution took \(CACurrentMediaTime() - start) seconds")
}

用法:

printExecutionTime(withTag: "Init") {
    // Do your work here
}

结果: #Init - execution took 1.00104497105349 seconds


2

您可以像这样测量纳秒

let startDate: NSDate = NSDate()

// your long procedure

let endDate: NSDate = NSDate()
let dateComponents: NSDateComponents = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian).components(NSCalendarUnit.CalendarUnitNanosecond, fromDate: startDate, toDate: endDate, options: NSCalendarOptions(0))
println("runtime is nanosecs : \(dateComponents.nanosecond)")

我只是在没有在“ //您的长程序”中放置任何代码的情况下运行了此程序,结果为240900993,即0.24秒。
user1021430 2014年

实例化一个NSCalendar昂贵的过程,但是您可以开始运行该过程之前执行该过程,因此不会将其添加到长时间过程的运行时中……请记住,调用创建NSDate实例和调用–components(_:, fromDate:)方法仍然需要时间,因此可能您将永远不会得到0.0纳秒。
Holex 2014年

从NSCalendar构造函数的外观来看,似乎可能无法提前创建它(startDate是必需的输入)。似乎真是猪,真是令人惊讶。
user1021430 2014年

您可以执行此操作(我更新后的答案),6.9在我的设备上无需任何其他步骤即可测得的时间约为微秒。
Holex 2014年

太好了,谢谢。现在,基本过程类似于JeremyP的回答,但是报告有所不同。
2014年

2

我用这个:

public class Stopwatch {
    public init() { }
    private var start_: NSTimeInterval = 0.0;
    private var end_: NSTimeInterval = 0.0;

    public func start() {
        start_ = NSDate().timeIntervalSince1970;
    }

    public func stop() {
        end_ = NSDate().timeIntervalSince1970;
    }

    public func durationSeconds() -> NSTimeInterval {
        return end_ - start_;
    }
}

我不知道它是否比以前发布的准确或多或少。但是秒数有很多小数点,并且似乎在使用swap()与自行实现swap等方法的QuickSort等算法中捕获了小的代码更改。

测试性能时,切记加快构建优化:

Swift编译器优化


2

这是我尝试的最简单答案:

let startTime = Date().timeIntervalSince1970  // 1512538946.5705 seconds

// time passes (about 10 seconds)

let endTime = Date().timeIntervalSince1970    // 1512538956.57195 seconds
let elapsedTime = endTime - startTime         // 10.0014500617981 seconds

笔记

  • startTime并且endTime属于类型TimeInterval,仅是typealiasfor Double,因此很容易将其转换为a Int或其他类型。时间以秒为单位进行测量,精确到毫秒。
  • 另请参阅DateInterval,其中包括实际的开始时间和结束时间。
  • 使用自1970年以来的时间类似于Java时间戳

最简单的方法,想用两个字符串,第二个是让elapsedTime = Date()。timeIntervalSince1970-startTime
FreeGor

1

我借鉴了Klaas的想法,创建了一个轻量级的结构来测量运行时间和间隔时间:

代码用法:

var timer = RunningTimer.init()
// Code to be timed
print("Running: \(timer) ") // Gives time interval
// Second code to be timed
print("Running: \(timer) ") // Gives final time

不必调用stop函数,因为print函数将给出所花费的时间。可能会反复调用它以获取经过的时间。但是要在代码中的特定位置停止计时器,timer.stop()还可以使用它返回以秒为单位的时间:let seconds = timer.stop() 计时器停止后,间隔计时器将不会,因此print("Running: \(timer) ")即使经过几行代码,计时器也会给出正确的时间。

以下是RunningTimer的代码。它已针对Swift 2.1进行了测试:

import CoreFoundation
// Usage:    var timer = RunningTimer.init()
// Start:    timer.start() to restart the timer
// Stop:     timer.stop() returns the time and stops the timer
// Duration: timer.duration returns the time
// May also be used with print(" \(timer) ")

struct RunningTimer: CustomStringConvertible {
    var begin:CFAbsoluteTime
    var end:CFAbsoluteTime

    init() {
        begin = CFAbsoluteTimeGetCurrent()
        end = 0
    }
    mutating func start() {
        begin = CFAbsoluteTimeGetCurrent()
        end = 0
    }
    mutating func stop() -> Double {
        if (end == 0) { end = CFAbsoluteTimeGetCurrent() }
        return Double(end - begin)
    }
    var duration:CFAbsoluteTime {
        get {
            if (end == 0) { return CFAbsoluteTimeGetCurrent() - begin } 
            else { return end - begin }
        }
    }
    var description:String {
    let time = duration
    if (time > 100) {return " \(time/60) min"}
    else if (time < 1e-6) {return " \(time*1e9) ns"}
    else if (time < 1e-3) {return " \(time*1e6) µs"}
    else if (time < 1) {return " \(time*1000) ms"}
    else {return " \(time) s"}
    }
}

1

将其包装在完成块中,以方便使用。

public class func secElapsed(completion: () -> Void) {
    let startDate: NSDate = NSDate()
    completion()
    let endDate: NSDate = NSDate()
    let timeInterval: Double = endDate.timeIntervalSinceDate(startDate)
    println("seconds: \(timeInterval)")
}

0

这是我想出的代码片段,似乎可以在带有Swift 4的Macbook上使用。

从未在其他系统上进行过测试,但是无论如何我还是值得分享的。

typealias MonotonicTS = UInt64
let monotonic_now: () -> MonotonicTS = mach_absolute_time

let time_numer: UInt64
let time_denom: UInt64
do {
    var time_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&time_info)
    time_numer = UInt64(time_info.numer)
    time_denom = UInt64(time_info.denom)
}

// returns time interval in seconds
func monotonic_diff(from: MonotonicTS, to: MonotonicTS) -> TimeInterval {
    let diff = (to - from)
    let nanos = Double(diff * time_numer / time_denom)
    return nanos / 1_000_000_000
}

func seconds_elapsed(since: MonotonicTS) -> TimeInterval {
    return monotonic_diff(from: since, to:monotonic_now())
}

以下是使用方法的示例:

let t1 = monotonic_now()
// .. some code to run ..
let elapsed = seconds_elapsed(since: t1)
print("Time elapsed: \(elapsed*1000)ms")

另一种方法是更明确地执行此操作:

let t1 = monotonic_now()
// .. some code to run ..
let t2 = monotonic_now()
let elapsed = monotonic_diff(from: t1, to: t2)
print("Time elapsed: \(elapsed*1000)ms")

0

这就是我写的方式。

 func measure<T>(task: () -> T) -> Double {
        let startTime = CFAbsoluteTimeGetCurrent()
        task()
        let endTime = CFAbsoluteTimeGetCurrent()
        let result = endTime - startTime
        return result
    }

要测量算法,请像这样使用它。

let time = measure {
    var array = [2,4,5,2,5,7,3,123,213,12]
    array.sorted()
}

print("Block is running \(time) seconds.")

那时间有多精确?因为我以前使用计时器,所以每0.1秒更新一次。我比较此答案和一个与此应答定时器和结果为0.1秒以上,然后用计时器..
马克西姆Kniazev

0

用于基本功能计时的静态Swift3类。它将按名称跟踪每个计时器。在您要开始测量的点上这样称呼它:

Stopwatch.start(name: "PhotoCapture")

调用此命令以捕获并打印经过的时间:

Stopwatch.timeElapsed(name: "PhotoCapture")

这是输出:***图像捕捉经过时间ms:1402.415125如果要使用nanos,则有一个“ useNanos”参数。请随时根据需要进行更改。

   class Stopwatch: NSObject {

  private static var watches = [String:TimeInterval]()

  private static func intervalFromMachTime(time: TimeInterval, useNanos: Bool) -> TimeInterval {
     var info = mach_timebase_info()
     guard mach_timebase_info(&info) == KERN_SUCCESS else { return -1 }
     let currentTime = mach_absolute_time()
     let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
     if useNanos {
        return (TimeInterval(nanos) - time)
     }
     else {
        return (TimeInterval(nanos) - time) / TimeInterval(NSEC_PER_MSEC)
     }
  }

  static func start(name: String) {
     var info = mach_timebase_info()
     guard mach_timebase_info(&info) == KERN_SUCCESS else { return }
     let currentTime = mach_absolute_time()
     let nanos = currentTime * UInt64(info.numer) / UInt64(info.denom)
     watches[name] = TimeInterval(nanos)
  }

  static func timeElapsed(name: String) {
     return timeElapsed(name: name, useNanos: false)
  }

  static func timeElapsed(name: String, useNanos: Bool) {
     if let start = watches[name] {
        let unit = useNanos ? "nanos" : "ms"
        print("*** \(name) elapsed \(unit): \(intervalFromMachTime(time: start, useNanos: useNanos))")
     }
  }

}


您与整个工作mach_timebase_info比你在做已经实现更好的源代码ProcessInfo.processInfo.systemUptime。只需这样做watches[name] = ProcessInfo.processInfo.systemUptime。而* TimeInterval(NSEC_PER_MSEC)如果你想毫微秒。
心教堂

0

基于富兰克林宇答案,心大教堂评论

细节

  • Xcode 10.1(10B61)
  • 斯威夫特4.2

解决方案1

测量(_:)

解决方案2

import Foundation

class Measurer<T: Numeric> {

    private let startClosure: ()->(T)
    private let endClosure: (_ beginningTime: T)->(T)

    init (startClosure: @escaping ()->(T), endClosure: @escaping (_ beginningTime: T)->(T)) {
        self.startClosure = startClosure
        self.endClosure = endClosure
    }

    init (getCurrentTimeClosure: @escaping ()->(T)) {
        startClosure = getCurrentTimeClosure
        endClosure = { beginningTime in
            return getCurrentTimeClosure() - beginningTime
        }
    }

    func measure(closure: ()->()) -> T {
        let value = startClosure()
        closure()
        return endClosure(value)
    }
}

解决方案2的用法

// Sample with ProcessInfo class

m = Measurer { ProcessInfo.processInfo.systemUptime }
time = m.measure {
    _ = (1...1000).map{_ in Int(arc4random()%100)}
}
print("ProcessInfo: \(time)")

// Sample with Posix clock API

m = Measurer(startClosure: {Double(clock())}) { (Double(clock()) - $0 ) / Double(CLOCKS_PER_SEC) }
time = m.measure {
    _ = (1...1000).map{_ in Int(arc4random()%100)}
}
print("POSIX: \(time)")

不知道为什么我们需要使用变量实现。Date不鼓励使用测量任何东西(但systemUptime还是clock()可以)。至于测试,我们已经有measure(_:)
心教堂

1
另一个注意事项:为什么将关闭设为可选?
心教堂

@Cœur,您说对了!感谢您的意见。我稍后会更新。
Vasily Bodnarchuk
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.