我们如何测量在 Swift 中运行一个函数所用的时间?我试图像这样显示经过的时间:“经过的时间是 0.05 秒”。看到 in 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")
}
sqrt(number)
而不是 number
处停止,您可以节省更多时间 - 但还有更多优化寻找素数的想法。
NSDate
个对象,并且可以测量它们之间的差异。
这是我编写的一个 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 用于计时功能:“由于与外部时间参考同步或由于用户显式更改时钟,系统时间可能会减少。”。
这是一个基于 CoreFoundation
s 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 ...
print("The task took \(timer.stop()) seconds.")
mach_absolute_time
没有遇到这个问题,所以我想知道为什么这个问题没有广为人知。也许只是因为它没有很好的记录。
使用时钟、ProcessInfo.systemUptime 或 DispatchTime 来获取简单的启动时间。
据我所知,至少有十种方法可以测量经过的时间:
基于单调时钟:
ProcessInfo.systemUptime。 mach_absolute_time 和 mach_timebase_info 如本答案所述。 POSIX 标准中的clock()。 POSIX 标准中的 times()。 (太复杂了,因为我们需要考虑用户时间与系统时间,并且涉及子进程。) JeremyP 在接受的答案中提到的 DispatchTime (马赫时间 API 的包装器)。 CACurrentMediaTime()。
基于挂钟:
(永远不要将它们用于度量:请参阅下面的原因)
其他人提到的 NSDate/Date。其他人提到的CFAbsoluteTime。调度墙时间。 POSIX 标准中的 gettimeofday()。
选项 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
的文档中:
重复调用此函数并不能保证结果单调递增。
原因类似于 currentTimeMillis
vs nanoTime
in Java:
您不能将其中一个用于其他目的。原因是没有计算机的时钟是完美的。它总是漂移,偶尔需要纠正。这种更正可能是手动进行的,或者在大多数机器的情况下,有一个进程正在运行并不断地对系统时钟(“挂钟”)进行小幅更正。这些往往会经常发生。每当有闰秒时,就会发生另一种这样的修正。
这里 CFAbsoluteTime
提供挂钟时间而不是启动时间。 NSDate
也是挂钟时间。
clock()
功能。
ProcessInfo.processInfo.systemUptime
、mach_absolute_time()
、DispatchTime.now()
和 CACurrentMediaTime()
方面取得了成功。但是 clock()
的解决方案没有正确转换为秒。因此,我建议将您的第一句话更新为:“使用 ProcessInfo.systemUptime 或 DispatchTime 获得准确的启动时间。”
clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
优化选项 2,它为您进行时基转换并提供纳秒值(请参阅 manpagez.com/man/3/clock_gettime_nsec_np)
斯威夫特 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
只需复制和粘贴此功能。用 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
}
let start = NSDate()
for index in 1...10000 {
// do nothing
}
let elapsed = start.timeIntervalSinceNow
// elapsed is a negative value.
您可以创建一个 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
iOS 13 似乎引入了一个与 DispatchTime
一起使用的新 API,无需手动计算两个时间戳之间的差异。
let start: DispatchTime = .now()
heavyTaskToMeasure()
let duration = start.distance(to: .now())
print(duration)
// prints: nanoseconds(NUMBER_OF_NANOSECONDS_BETWEEN_TWO_TIMESTAMPS)
遗憾的是,没有提供文档,但在做了一些测试之后,看起来总是返回 .nanoseconds
案例。
通过一个简单的扩展,您可以将 DispatchTimeInterval
转换为 TimeInterval
。 credit
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
}
}
}
用于测量闭包执行时间的简单辅助函数。
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
您可以像这样测量纳秒:
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)")
NSCalendar
是昂贵的过程,但您可以在开始运行过程之前执行它,因此它不会被添加到长过程的运行时...请记住调用创建NSDate
实例和调用 –components(_:, fromDate:)
方法仍然需要时间,因此您可能永远不会得到 0.0
纳秒。
6.9
微秒。
我用这个:
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 与实现自己的交换等。
请记住在测试性能时加快构建优化:
https://i.stack.imgur.com/u5hw3.png
这是我尝试最简单的答案:
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 类型,它只是 Double 的类型别名,因此很容易将其转换为 Int 或其他类型。时间以秒为单位测量,精度为亚毫秒。
另请参阅 DateInterval,其中包括实际的开始时间和结束时间。
使用自 1970 年以来的时间类似于 Java 时间戳。
我借鉴了 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"}
}
}
将其包裹在完成块中以便于使用。
public class func secElapsed(completion: () -> Void) {
let startDate: NSDate = NSDate()
completion()
let endDate: NSDate = NSDate()
let timeInterval: Double = endDate.timeIntervalSinceDate(startDate)
println("seconds: \(timeInterval)")
}
The recommend way 使用 XCTest
中可用的 measure
函数来检查经过的时间/性能。
编写自己的度量块是不可靠的,因为代码块的性能(以及因此执行/经过的时间)受例如 CPU 缓存的影响。
第二次调用函数时,可能会比第一次调用时快,although it can vary a few %。因此,通过执行一次您自己的闭包(这里到处都给出)来进行“基准测试”,可以得到与真实用户在生产环境中执行的代码不同的结果。
measure
函数多次调用您的代码块,模仿您的代码的性能/运行时间,就像在生产中使用的一样 (at least gives more accurate results)。
这是我想出的片段,它似乎适用于我的 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 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))")
}
}
}
ProcessInfo.processInfo.systemUptime
的 source code 相比,您使用 mach_timebase_info
完成的整个工作已经实现得更好。所以只需执行 watches[name] = ProcessInfo.processInfo.systemUptime
。如果你想要纳米,* TimeInterval(NSEC_PER_MSEC)
。
基于 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)")
类似于 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)")
}
}
}
不定期副业成功案例分享
Date
,果然报告了 41.87 秒。我不知道uptimeNanoseconds
在做什么,但它没有报告正确的持续时间。