156

我们如何测量在 Swift 中运行一个函数所用的时间?我试图像这样显示经过的时间:“经过的时间是 0.05 秒”。看到在 Java 中,我们可以使用 System.nanoTime(),在 Swift 中是否有任何等效的方法可以实现这一点?

请看一下示例程序:

func isPrime(_ 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) {
    print("Prime number")
} else {
    print("NOT a prime number")
}
4

20 回答 20

268

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

从 Swift 3 开始,现在有一个“swiftified”的 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 用于计时功能:“由于与外部时间参考同步或由于用户明确更改时钟,系统时间可能会减少。 ”。

于 2014-07-15T10:35:01.523 回答
75

这是一个基于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.")
于 2014-10-26T21:30:41.120 回答
62

使用clockProcessInfo.systemUptimeDispatchTime表示简单的启动时间。


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

基于单调时钟:

  1. ProcessInfo.systemUptime.
  2. mach_absolute_timemach_timebase_infothis answer中所述。
  3. clock()POSIX 标准中。
  4. times()POSIX 标准中。(太复杂了,因为我们需要考虑用户时间与系统时间,并且涉及到子进程。)
  5. DispatchTimeJeremyP 在接受的答案中提到的(马赫时间 API 的包装器)。
  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

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

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

于 2016-05-26T02:44:23.630 回答
49

斯威夫特 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")
}

用法

measureTime(for: <insert method signature here>)

控制台日志

Took xx.xxxxx seconds
于 2018-02-07T22:28:51.527 回答
17

只需复制和粘贴此功能。用 swift 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
}
于 2019-05-30T16:16:57.657 回答
15
let start = NSDate()

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

let elapsed = start.timeIntervalSinceNow

// elapsed is a negative value.
于 2015-06-22T09:03:33.703 回答
6

您可以创建一个time函数来衡量您的通话。我受到Klaas 的回答的启发。

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

于 2015-01-16T18:34:20.113 回答
4

看起来iOS 13引入了一个新的 API 来使用,DispatchTime它消除了手动计算两个时间戳之间差异的需要。

distance(to:)

let start: DispatchTime = .now()
heavyTaskToMeasure()
let duration = start.distance(to: .now())
print(duration) 
// prints: nanoseconds(NUMBER_OF_NANOSECONDS_BETWEEN_TWO_TIMESTAMPS)

遗憾的是,没有提供文档,但在做了一些测试之后,似乎.nanoseconds总是返回案例。

通过一个简单的扩展,您可以DispatchTimeIntervalTimeInterval. 信用

extension TimeInterval {
    init?(dispatchTimeInterval: DispatchTimeInterval) {
        switch dispatchTimeInterval {
        case .seconds(let value):
            self = Double(value)
        case .milliseconds(let value):
            self = Double(value) / 1_000
        case .microseconds(let value):
            self = Double(value) / 1_000_000
        case .nanoseconds(let value):
            self = Double(value) / 1_000_000_000
        case .never:
            return nil
        }
    }
}
于 2021-11-16T12:20:44.913 回答
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

于 2017-12-13T17:07:32.840 回答
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)")
于 2014-07-15T10:35:08.203 回答
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() 的 QuickSort 与实现自己的交换等。

请记住在测试性能时加快构建优化:

Swift 编译器优化

于 2015-06-19T19:33:13.027 回答
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,因此很容易将其转换为 anInt或其他类型。时间以秒为单位测量,精度为亚毫秒。
  • 另请参阅DateInterval,其中包括实际的开始和结束时间。
  • 使用自 1970 年以来的时间类似于Java 时间戳
于 2017-12-06T05:12:28.083 回答
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

不必调用停止函数,因为打印函数会给出经过的时间。它可能会被重复调用以获取时间流逝。但是要在代码中的某个点停止计时器,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"}
    }
}
于 2015-12-16T22:00:46.693 回答
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)")
}
于 2016-03-08T06:37:26.480 回答
1

检查经过时间/性能的推荐方法measure是使用 s中可用的功能XCTest

编写自己的度量块是不可靠的,因为代码块的性能(以及因此执行/经过的时间)受例如 CPU 缓存的影响。

第二次调用函数时,可能会比第一次调用更快,尽管它可能会有所不同 %。因此,通过执行一次您自己的闭包(这里到处都给出)来进行“基准测试”,可以得到与真实用户在生产环境中执行的代码不同的结果。

该函数多次measure调用您的代码块,模仿代码的性能/运行时间,就像它在生产中使用一样(至少给出更准确的结果)。

于 2021-01-23T15:26:47.223 回答
0

这是我想出的片段,它似乎适用于我的 Macbook 和 Swift 4。

从未在其他系统上测试过,但我认为无论如何都值得分享。

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")
于 2018-03-25T12:32:08.650 回答
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.")
于 2018-04-02T23:45:39.607 回答
0

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

Stopwatch.start(name: "PhotoCapture")

调用它来捕获并打印经过的时间:

Stopwatch.timeElapsed(name: "PhotoCapture")

这是输出: *** PhotoCapture elapsed 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))")
     }
  }

}

于 2018-05-17T16:35:40.703 回答
0

基于Franklin Yu的回答和Cœur评论

细节

  • 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)")
于 2018-06-26T14:32:22.710 回答
0

类似于 C# 的 Stopwatch 类的解决方案。

用法:

let s = Stopwatch()
s.start()
    
for _ in 1..<5 {
    Thread.sleep(forTimeInterval: 5)
    print("Sleeped \(s.elapsedS) S")
}
    
s.stop()
print("Sleeped \(s.elapsedS) s")

结果:

Sleeped 5.001374235 s
Sleeped 10.002803108 s
Sleeped 15.003942841 s
Sleeped 20.004250347000003 s
Sleeped 20.004299962 s

代码:


public class Stopwatch {
    public var elapsedS : Double {
        return elapsedMs/1000
    }
    
    public var elapsedMs: Double {
        guard let startTime = startTime else { return 0 }
        
        if isGoing {
            let end = DispatchTime.now()
            let nanoTime = end.uptimeNanoseconds - startTime.uptimeNanoseconds
            
            return Double(nanoTime) / 1000000
        }
        
        if let endTime = endTime {
            let nanoTime = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds
            return Double(nanoTime) / 1000000
        }
        
        return 0
    }
    
    private var startTime : DispatchTime? = nil
    private var endTime : DispatchTime? = nil
    private var isGoing = false
    
    public init () { }
    
    public func start() -> Stopwatch {
        isGoing = true
        startTime = DispatchTime.now()
        
        return self
    }
    
    public func stop() {
        endTime = DispatchTime.now()
        isGoing = false
    }
    
    public func printS(_ str : String? = nil){
        if let str = str {
            print("\(str): \(elapsedS)")
        }
        else {
            print("\(elapsedS)")
        }
    }
    
    public func printMs(_ str : String? = nil){
        if let str = str {
            print("\(str): \(elapsedMs)")
        }
        else {
            print("\(elapsedMs)")
        }
    }
}
于 2021-01-26T15:50:01.327 回答