Wie kann man die Ausführung von Swift-Code bewerten?

77

Gibt es eine andere Möglichkeit / Software, um die genaue Zeit anzugeben, die zum Ausführen eines in Swift geschriebenen Codeblocks erforderlich ist, außer den folgenden?

let date_start = NSDate()

// Code to be executed 

println("\(-date_start.timeIntervalSinceNow)")
ielyamani
quelle
1
... du meinst etwas anderes als Instrumente zu benutzen? Sie wollen dieses Zeug eigentlich für sich selbst bekommen? Sie wollen es nicht nur als Mensch wissen, Sie brauchen Ihren Code, um es zu erarbeiten?
Tommy

Antworten:

135

Wenn Sie nur eine eigenständige Timing-Funktion für einen Codeblock benötigen, verwende ich die folgenden Swift-Hilfsfunktionen:

func printTimeElapsedWhenRunningCode(title:String, operation:()->()) {
    let startTime = CFAbsoluteTimeGetCurrent()
    operation()
    let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
    print("Time elapsed for \(title): \(timeElapsed) s.")
}

func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double {
    let startTime = CFAbsoluteTimeGetCurrent()
    operation()
    let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
    return Double(timeElapsed)
}

Ersteres protokolliert die für einen bestimmten Codeabschnitt erforderliche Zeit, wobei letzteres diese als Float zurückgibt. Als Beispiel für die erste Variante:

printTimeElapsedWhenRunningCode(title:"map()") {
    let resultArray1 = randoms.map { pow(sin(CGFloat($0)), 10.0) }
}

wird sich abmelden wie:

Verstrichene Zeit für map (): 0.0617449879646301 s

Beachten Sie, dass die Swift-Benchmarks je nach ausgewähltem Optimierungsgrad stark variieren. Dies kann daher nur für relative Vergleiche der Swift-Ausführungszeit hilfreich sein. Auch das kann sich pro Beta-Version ändern.

Brad Larson
quelle
3
Vielen Dank, Ihre Antwort ist sehr hilfreich
ielyamani
Ein Problem bei dieser Technik besteht darin, dass ein neuer Bereich eingeführt wird, sodass alles, was in diesem Bereich deklariert ist, außerhalb des Bereichs nicht verfügbar ist (z. B. in Ihrem Beispiel ist resultArray1 nach dem Schließen nicht für den Code verfügbar.
pbouf77
36

Wenn Sie einen Einblick in die Leistung eines bestimmten Codeblocks erhalten und sicherstellen möchten, dass die Leistung bei Änderungen nicht beeinträchtigt wird , verwenden Sie am besten die Messleistungsfunktionen von XCTest , z measure(_ block: () -> Void).

Schreiben Sie einen Komponententest, der die Methode ausführt, die Sie vergleichen möchten. Dieser Komponententest führt ihn mehrmals aus, sodass Sie Zeit benötigen und die Ergebnisse abweichen

func testExample() {

    self.measure {
        //do something you want to measure
    }
}

Weitere Informationen finden Sie in den Apple-Dokumenten unter Testen mit Xcode -> Leistungstests

kviksilver
quelle
6
Beachten Sie measureBlock, dass der Blockcode mehrmals ausgeführt wird, um bessere Statistiken zu erhalten.
Eneko Alonso
was ist measureBlock? Woher kommt es (was soll ich importieren)? in Swift 4
user924
MeasureBlock () ist eine Methode in XCTestCase. import XCTest ... Datei -> neu -> Testfallvorlage wird dieser Block standardmäßig hinzugefügt
kviksilver
16

Mit dieser Funktion können Sie sowohl asynchronen als auch synchronen Code messen:

import Foundation

func measure(_ title: String, block: (@escaping () -> ()) -> ()) {

    let startTime = CFAbsoluteTimeGetCurrent()

    block {
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        print("\(title):: Time: \(timeElapsed)")
    }
}

Im Grunde übergeben Sie also einen Block, der eine Funktion als Parameter akzeptiert, mit der Sie messen, wann zu beenden ist.

Um beispielsweise zu messen, wie lange ein Aufruf mit dem Namen "myAsyncCall" dauert, würden Sie ihn folgendermaßen aufrufen:

measure("some title") { finish in
    myAsyncCall {
        finish()
    }
    // ...
}

Für synchronen Code:

measure("some title") { finish in
     // code to benchmark
     finish()
     // ...
}

Dies sollte MeasureBlock von XCTest ähneln, obwohl ich nicht weiß, wie genau es dort implementiert ist.

Ixx
quelle
@scaping wird benötigt
Cameron Lowell Palmer
8

Benchmarking-Funktion - Swift 4.2

Dies ist eine unglaublich vielseitige Benchmarking-Funktion, die es ermöglicht, Tests zu kennzeichnen, viele Tests durchzuführen und ihre Ausführungszeiten zu mitteln. Dies ist ein Setup-Block, der zwischen den Tests aufgerufen wird (dh das Mischen eines Arrays zwischen dem Messen eines Sortieralgorithmus) und das klare Drucken des Benchmarking Ergebnisse, und es gibt auch die durchschnittliche Ausführungszeit als Double.

Versuche Folgendes:

@_transparent @discardableResult public func measure(label: String? = nil, tests: Int = 1, printResults output: Bool = true, setup: @escaping () -> Void = { return }, _ block: @escaping () -> Void) -> Double {

    guard tests > 0 else { fatalError("Number of tests must be greater than 0") }

    var avgExecutionTime : CFAbsoluteTime = 0
    for _ in 1...tests {
        setup()
        let start = CFAbsoluteTimeGetCurrent()
        block()
        let end = CFAbsoluteTimeGetCurrent()
        avgExecutionTime += end - start
    }

    avgExecutionTime /= CFAbsoluteTime(tests)

    if output {
        let avgTimeStr = "\(avgExecutionTime)".replacingOccurrences(of: "e|E", with: " × 10^", options: .regularExpression, range: nil)

        if let label = label {
            print(label, "▿")
            print("\tExecution time: \(avgTimeStr)s")
            print("\tNumber of tests: \(tests)\n")
        } else {
            print("Execution time: \(avgTimeStr)s")
            print("Number of tests: \(tests)\n")
        }
    }

    return avgExecutionTime
}

Verwendung

var arr = Array(1...1000).shuffled()

measure(label: "Map to String") {
    let _ = arr.map { String($0) }
}

measure(label: "Apple Shuffle", tests: 1000, setup: { arr.shuffle() }) {
    arr.sort()
}

measure {
    let _ = Int.random(in: 1...10000)
}

let mathExecutionTime = measure(printResults: false) {
    let _ = 219 * 354
}

print("Math Execution Time: \(mathExecutionTime * 1000)ms")


// Prints:
//
// Map to String ▿
//     Execution time: 0.021643996238708496s
//     Number of tests: 1
//
// Apple's Sorting Method ▿
//     Execution time: 0.0010601345300674438s
//     Number of tests: 1000
//
// Execution time: 6.198883056640625 × 10^-05s
// Number of tests: 1
//
// Math Execution Time: 0.016927719116210938ms
//

Hinweis: measure Gibt auch die Ausführungszeit zurück. Die label, testsund setupArgumente sind optional. Das printResultsArgument ist truestandardmäßig eingestellt.

Noah Wilder
quelle
3

Ich mag Brad Larsons Antwort auf einen einfachen Test, den Sie sogar auf einem Spielplatz durchführen können. Für meine eigenen Bedürfnisse habe ich es ein bisschen optimiert:

  1. Ich habe den Aufruf der Funktion, die ich testen möchte, in eine Testfunktion eingeschlossen, die mir Raum gibt, mit verschiedenen Argumenten herumzuspielen, wenn ich möchte.
  2. Die Testfunktion gibt ihren Namen zurück, sodass ich den Parameter 'title' nicht in die averageTimeTo()Benchmarking-Funktion aufnehmen muss.
  3. Mit der Benchmarking-Funktion können Sie optional festlegen, wie viele Wiederholungen ausgeführt werden sollen (mit einem Standardwert von 10). Anschließend werden sowohl die Gesamt- als auch die Durchschnittszeit angegeben.
  4. Die Benchmarking-Funktion wird auf der Konsole gedruckt UND gibt die zurück averageTime(was Sie von einer Funktion namens 'durchschnittlichTimeTo' erwarten können), sodass keine zwei separaten Funktionen mit nahezu identischer Funktionalität erforderlich sind.

Zum Beispiel:

func myFunction(args: Int...) {
    // Do something
}

func testMyFunction() -> String {
    // Wrap the call to myFunction here, and optionally test with different arguments
    myFunction(args: 1, 2, 3)
    return #function
}

// Measure average time to complete test
func averageTimeTo(_ testFunction: () -> String, repeated reps: UInt = 10) -> Double {
    let functionName = testFunction()
    var totalTime = 0.0
    for _ in 0..<reps {
        let startTime = CFAbsoluteTimeGetCurrent()
        testFunction()
        let elapsedTime = CFAbsoluteTimeGetCurrent() - startTime
        totalTime += elapsedTime
    }
    let averageTime = totalTime / Double(reps)
    print("Total time to \(functionName) \(reps) times: \(totalTime) seconds")
    print("Average time to \(functionName): \(averageTime) seconds\n")
    return averageTime
}

averageTimeTo(testMyFunction)

// Total time to testMyFunction() 10 times: 0.000253915786743164 seconds
// Average time to testMyFunction(): 2.53915786743164e-05 seconds

averageTimeTo(testMyFunction, repeated: 1000)

// Total time to testMyFunction() 1000 times: 0.027538537979126 seconds
// Average time to testMyFunction(): 2.7538537979126e-05 seconds
Kal
quelle
1
Das ist ziemlich klug! Und danke fürs Teilen. @discardableResultkönnte nützlich sein, da Sie bereits dieaverageTime
ielyamani
Ja, guter Punkt, das wäre schneller, wenn Sie nicht die Gewohnheit haben, den Rückgabewert zu verwenden (wie in meinem Beispiel gezeigt!). Ich habe es nicht einmal bemerkt, da es auf Spielplätzen keine Warnung erzeugt.
Kal
0

Sie haben mehrere Möglichkeiten

  • CFAbsoluteTimeGetCurrent
  • Date().timeIntervalSince1970
  • DispatchTime.now().uptimeNanoseconds (und div am 1_000_000)
  • XCTest
let metrics: [XCTMetric] = [XCTMemoryMetric(), XCTStorageMetric(), XCTClockMetric()]

let measureOptions = XCTMeasureOptions.default
measureOptions.iterationCount = 3

measure(metrics: metrics, options: measureOptions) {
    //block of code
}

* Ich würde Ihnen nicht empfehlen, eine allgemeine Funktion mit measureBlock zu erstellen , da Xcode manchmal nicht richtig damit umgehen kann. Verwenden Sie deshalb measureBlock für jeden Leistungstest

yoAlex5
quelle