在 Swift 中度量运行时间

我们如何测量在 Swift 中运行一个函数所花费的时间?我试图像这样显示经过的时间: “经过的时间是0.05秒”。看到 用爪哇语,我们可以使用 System.nanTime () ,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")
}
129465 次浏览

更新

有了 Swift 5.7,我认为下面的所有东西都会过时。Swift 5.7引入了 时钟的概念,时钟似乎有一个功能被设计用来完成这里所要求的任务。

一旦我有了 Swift 5.7并且有时间重写它,我就会更新一个例子。


下面是我写的一个 Swift 函数,用来测量 Swift 中的 欧拉计划问题

至于 Swift 3,现在有一个版本的中央车站是“迅速”。所以正确的答案可能是使用 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 计时函数: “ 由于与外部时间引用同步,或由于显式用户更改时钟,系统时间可能会减少。”。

你可以这样测量 纳秒:

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

这是一个基于 CoreFoundationsCFAbsoluteTime的方便的定时器类:

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 ...


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

您可以创建一个 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)).duration

我用这个:

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

我不知道它是否比之前发布的更准确。但是这些秒有很多小数,似乎可以捕捉像使用交换()或自己实现交换等算法中的小代码更改。

在测试性能时,请记住加大构建优化的力度:

Swift compiler optimizations

let start = NSDate()


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


let elapsed = start.timeIntervalSinceNow


// elapsed is a negative value.

我从 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"}
}
}

将它包装在一个完成块中,以便于使用。

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

使用 clockProcessInfo.systemUptimeDispatchTime进行简单的启动时间。


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

基于单调时钟:

  1. ProcessInfo.systemUptime .
  2. mach_absolute_time 使用 mach_timebase_info,正如本文中提到的那样 回答
  3. POSIX 标准中的 clock()
  4. POSIX 标准。(太复杂,因为我们需要 考虑用户时间和系统时间,子进程是 )
  5. 正如 JeremyP 在公认的答案中提到的,DispatchTime (Mach time API 的包装器)。
  6. CACurrentMediaTime() .

挂钟基于:

(永远不要使用这些指标: 参见下面的原因)

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

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

备选方案1: 基础工艺信息 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 中的 ABC0与 nanoTime:

你不能用它来做其他事情,原因就是不行 计算机的时钟是完美的,它总是漂移,偶尔 需要纠正。这种纠正可能会发生 手动操作,或者在大多数机器的情况下,有一个过程 运行并不断地对系统时钟进行小的修正 (“挂钟”)。这些往往发生频繁。另一个这样的修正 只要有闰秒就会发生。

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

下面是我试图给出的最简单的答案:

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

笔记

  • startTimeendTime属于 TimeInterval类型,它只是 Doubletypealias,所以很容易将它转换成 Int或其他类型。时间以秒为单位,精度为毫秒以下。
  • 另请参阅 DateInterval,其中包括实际的开始和结束时间。
  • 自1970年以来使用的时间类似于 Java 时间戳

用于测量执行时间和闭包的简单助手函数。

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

迅捷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

这是我想出来的代码片段,它似乎适用于我的 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")

我是这么写的。

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

用于基本函数计时的静态 Swift3类。它将按名称跟踪每个计时器。在你想开始测量的时候,可以这样说:

Stopwatch.start(name: "PhotoCapture")

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

Stopwatch.timeElapsed(name: "PhotoCapture")

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

}

根据 Franklin Yu的回答和 你好的评论

细节

  • Xcode 10.1(10B61)
  • Swift 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)")

只需复制和粘贴这个函数。 模仿 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
}

检查运行时间/性能的推荐方法是使用 XCTest中的 measure函数。

编写自己的度量块是不可靠的,因为代码块的性能(因此执行/运行时间)受到 CPU 缓存等因素的影响。

第二次调用函数时,很有可能会比第一次调用 虽然它可以变化百分之几更快。因此,通过使用您自己的闭包(这里给出了所有的地方)执行一次“基准测试”,可以得到与您的代码在生产环境中由真正的用户执行时不同的结果。

measure函数调用代码块 好几次,模仿生产中使用的代码(至少给出了更准确的结果)的性能/运行时间。

看起来 IOS13引入了一个新的 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用例。

通过一个简单的扩展,您可以将 DispatchTimeInterval转换为 TimeInterval.信用卡

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

这是一个很老的问题,但是为什么不用路标来测量呢