Swiftで経過時間を測定する


132

Swiftで関数を実行するために経過した時間をどのように測定できますか?「経過時間は.05秒」のように経過時間を表示しようとしています。見たJavaで、我々はSystem.nanoTimeの()を使用することができ、任意の同等の方法は、これを達成するためにスウィフトで利用可能であるのですか?

サンプルプログラムをご覧ください。

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

@holex使用されているアルゴリズムを無視してください。経過時間を測定する方法を理解しようとしていますか?
Vaquita 14

1
NSDateオブジェクトを使用して、オブジェクト間の違いを測定できます。
holex

4
XCodeを使用している場合は、新しいパフォーマンステスト機能を使用することをお勧めします。それはあなたのためにすべての重労働を行い、それを複数回実行し、標準偏差で平均時間を与えます...
Roshan

1
@dumbledad単体テストでブロック全体のパフォーマンスを直接測定できます。たとえば、こちらをご覧ください。ランをさらに細かく分解したい場合(たとえば、行ごとのコード)、Instrumentsの一部であるTime Profilerを確認してください。これははるかに強力で包括的です。
Roshan

回答:


229

これは、SwiftでProject Eulerの問題を測定するために書いたSwift関数です

Swift 3の時点で、「swift化」されたバージョンの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

6
swift3が機能しません。0.0114653027が返されますが、これはtrueではないという事実でわかります。日付のあるものは4.77720803022385秒を返します
Cristi Băluță

5
残念ながら違います。なんらかの理由で稼働時間が完全になくなっています。1004959766ナノ秒(約1秒)かかったというテストがありますが、間違いなく約40秒間実行されていました。私はDate一緒に使用しましたが、41.87秒を報告するのに十分です。何uptimeNanosecondsをしているのかわかりませんが、正しい継続時間を報告していません。
jowie 16

2
大きな編集で申し訳ありませんが、新しいソリューションを優先していますが、NSDateコードを完全に削除することをお勧めします。NSDateを使用することは完全に信頼できません。NSDateはシステムクロックに基づいており、さまざまな理由によりいつでも変更できます。ネットワーク時間同期(NTP)によるクロックの更新(ドリフトを調整するために頻繁に発生)、DST調整、うるう秒、およびユーザーによるクロックの手動調整。さらに、Swift 1を使用してAppStoreでアプリを公開することはできなくなります。Swift3が最低限必要です。したがって、「やらないで」と言う以外に、NSDateコードを保持する意味はありません。
・クール

1
NSDateバージョンに対する批判は有効であり(DSTの変更が常にGMTである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
詳細な説明:「この関数を繰り返し呼び出しても、単調に増加する結果が保証されるわけではありません。外部の時間参照との同期や、ユーザーによる時計の明示的な変更により、システム時間が減少する場合があります。」
Klaas

開発者は、「外部時間参照との同期」と「時計の明示的なユーザー変更」を考慮に入れる必要があります。2つの状況が発生することは不可能だということですか?
フランクリンYu

@FranklinYuいいえ、私はこれらが2つの考えられる理由であることを述べたかっただけです。常に単調に増加する値を取得することに依存している場合は、他のオプションがあります。
Klaas、2016年

時々私は短い期間を測定したいと思います。その間に同期が発生すると、負の時間になる可能性があります。実際mach_absolute_timeにはこの問題に悩まされていないものがあるので、なぜそれが広く知られていないのか疑問に思います。多分それは十分に文書化されていないからです。
フランクリンユー

54

、、またはを使用してclock、簡単な起動時間を確保します。ProcessInfo.systemUptimeDispatchTime


私の知る限り、経過時間を測定する方法は少なくとも10通りあります。

単調時計ベース:

  1. ProcessInfo.systemUptime
  2. mach_absolute_timemach_timebase_info述べたように、この答え
  3. clock()POSIX標準
  4. times()POSIX標準。(ユーザー時間とシステム時間を考慮する必要があり、子プロセスが関与するため、複雑すぎます。)
  5. DispatchTime (マッハタイムAPIのラッパー)受け入れられた回答でJeremyPによって言及されています。
  6. CACurrentMediaTime()

壁掛け時計ベース:

(それらをメトリックに使用しないでください:以下の理由を参照してください)

  1. NSDate/ Date他の人が言及したように。
  2. CFAbsoluteTime 他の人が述べたように。
  3. DispatchWallTime
  4. gettimeofday()POSIX標準

オプション1、2、3については以下で詳しく説明します。

オプション1:Foundationでプロセス情報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

この関数を繰り返し呼び出しても、単調に増加する結果は保証されません。

理由は、JavacurrentTimeMillisvs nanoTime似ています

他の目的に使用することはできません。その理由は、コンピュータの時計が完璧ではないためです。それは常にドリフトし、時々修正する必要があります。この修正は手動で行われるか、またはほとんどのマシンの場合、実行されてシステムクロック(「ウォールクロック」)に継続的に小さな修正を発行するプロセスがあります。これらは頻繁に発生する傾向があります。うるう秒があるときはいつでも、このような別の修正が行われます。

ここでCFAbsoluteTimeは、起動時間の代わりに実時間を提供します。NSDate壁時計の時間でもあります。


最良の答え-しかし、一体、何をするのが最善のアプローチですか?
Fattie

1
オプション5は私に最適です。マルチプラットフォームコードを検討している場合は、これを試してください。DarwinとGlibcの両方のライブラリにclock()機能があります。
Misha Svyatoshenko 2017

システムクロックベースのソリューションのために:私は成功を収め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秒経過したようなものを出力します(もちろん、時間はタスクによって異なります。この測定の10進精度レベルを確認するためにこれを示しています)。

これから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?
マクシムクニアゼフ2018年

1
@MaksimKniazev負の値なので、人々は正の値を求めます!
thachnb

@thachnb「startingPoint.timeIntervalSinceNow」が負の値を生成するという考えはありませんでした...
Maksim Kniazev

1
@MaksimKniazev Appleは次のように述べています:日付オブジェクトが現在の日時より前の場合、このプロパティの値は負になります。
Peter Guan

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

この関数をコピーして貼り付けるだけです。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
}

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
「この関数を繰り返し呼び出しても、単調に増加する結果は保証されません。」ドキュメンテーションによると。
フランクリンYu

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

"// your long procedure"にコードを配置せずにこれを実行しただけで、240900993の結果(0.24秒)が得られました。
user1021430

NSCalendarは高価なプロシージャをインスタンス化しますが、プロシージャの実行を開始する前にNSDateインスタンスを作成できるため、長いプロシージャのランタイムに追加されません... インスタンスの作成の呼び出しと–components(_:, fromDate:)メソッドの呼び出しにはまだ時間がかかることを覚えておいてください。0.0ナノ秒は得られません。
holex

NSCalendarコンストラクターの外観から、事前に作成することはできない可能性があるようです(startDateは必須入力です)。それがそんなに独り占めのようであるのは、ちょっと意外です。
user1021430

あなたはこれを行うことができます(私の更新された答え)、追加の手順なしで測定された時間は6.9私のデバイスで約マイクロ秒です。
holex 2014

よろしくお願いします。基本的な手順はJeremyPの回答に似ていますが、レポートは異なります。
user1021430 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()を使用するQuickSortとswap urselfを実装するなどのアルゴリズムの小さなコード変更をキャッチしているようです。

パフォーマンスをテストするときは、ビルドの最適化を必ず上げてください。

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なので、に変換するのは簡単Intです。時間はミリ秒未満の精度で秒単位で測定されます。
  • DateInterval実際の開始時間と終了時間を含むも参照してください。
  • 1970年以降の時刻の使用は、Javaタイムスタンプに似ています

最も簡単な方法は、2つの文字列にしたい場合です。2番目は、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

print関数が経過時間を提供するため、stop関数を呼び出す必要はありません。経過時間を取得するために繰り返し呼び出される場合があります。しかし、コードの特定の時点でタイマーを停止するために、タイマーを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")

もう1つの方法は、より明示的に行うことです。

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秒ごとに更新するためにタイマーを使用していたためです。...タイマーをもっとしている私は、この答えを比較し、この回答からタイマーと結果との1は0.1秒である
マクシムKniazev

0

基本的な関数のタイミングのための静的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))")
     }
  }

}


でのすべての作業mach_timebase_infoは、のソースコードで行った作業よりも優れていますProcessInfo.processInfo.systemUptime。したがって、単に行いますwatches[name] = ProcessInfo.processInfo.systemUptime。そして* TimeInterval(NSEC_PER_MSEC)、ナノが欲しいなら。
・クール

0

フランクリン・ユーの回答とクールのコメントに基づく

細部

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

変数の実装を使用する必要がある理由がわかりません。使用してDate何かを測定することは非常に落胆(ただしれるsystemUptimeか、clock()OKです)。テストに関しては、measure(_:)すでにあります。
・クール

1
別のメモ:クロージャーをオプションにする理由
・クール

@Curur right!コメントしてくれてありがとう。投稿は後で更新します。
Vasily Bodnarchuk
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.