Як порівняти виконання коду Swift?


80

Чи існує спосіб / програмне забезпечення, щоб дати точний час, необхідний для виконання блоку коду, написаного Swift, крім наступного?

let date_start = NSDate()

// Code to be executed 

println("\(-date_start.timeIntervalSinceNow)")

1
... Ви маєте на увазі, крім використання Інструментів? Ви насправді хочете отримати ці речі для себе? Ви не просто хочете знати його як людину, вам потрібен ваш код, щоб мати змогу це опрацювати?
Tommy

Відповіді:


140

Якщо вам просто потрібна автономна функція синхронізації для блоку коду, я використовую такі допоміжні функції Swift:

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

Перший вийде з часу, необхідного для даного розділу коду, а другий поверне його як плаваючий. Як приклад першого варіанту:

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

вийде щось на зразок:

Час, що минув для карти (): 0,0617449879646301 с

Майте на увазі, що тести Swift сильно відрізнятимуться залежно від вибраного вами рівня оптимізації , тому це може бути корисним лише для відносного порівняння часу виконання Swift. Навіть це може змінитися залежно від бета-версії.


4
Дякую, ваша відповідь дуже корисна
ielyamani

Проблема цієї методики полягає в тому, що вона вводить нову область дії, тому все, що оголошено в цій області, не буде доступним за її межами (наприклад, у вашому прикладі resultArray1 не буде доступним для кодування після закриття.
pbouf77,

37

Якщо ви хочете отримати уявлення про продуктивність певного блоку коду та переконатися, що продуктивність не заважає вам, коли ви редагуєте, найкраще буде використовувати функції вимірювання продуктивності XCTest , наприклад measure(_ block: () -> Void).

Напишіть модульний тест, який виконує метод, який ви хочете порівняти, і цей модульний тест буде запускати його кілька разів, даючи вам необхідний час та відхилення результатів

func testExample() {

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

Ви можете знайти більше інформації в документації на Apple у розділі Тестування за допомогою Xcode -> Тестування продуктивності


7
Майте на увазі measureBlock, що виконуватиме блоковий код кілька разів, щоб отримати кращу статистику.
Енеко Алонсо

що це measureBlock? звідки він береться (що слід імпортувати)? у Swift 4
924,

measureBlock () - це метод на XCTestCase. імпортувати XCTest ... Файл -> новий -> шаблон тестового випадку буде додавати цей блок за замовчуванням
kviksilver

17

Ви можете використовувати цю функцію для вимірювання асинхронного, а також синхронного коду:

import Foundation

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

    let startTime = CFAbsoluteTimeGetCurrent()

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

Отже, в основному ви передаєте блок, який приймає функцію як параметр, який ви використовуєте, щоб вказати, коли потрібно закінчити.

Наприклад, щоб виміряти, скільки часу триває якийсь виклик, який називається "myAsyncCall", ви б назвали його так:

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

Для синхронного коду:

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

Це має бути подібним до вимірювання blockBlock від XCTest, хоча я не знаю, як саме це там реалізовано.


Потрібно @escaping
Камерон Лоуелл Палмер,

9

Функція порівняльного аналізу - Swift 4.2

Це неймовірно універсальна функція бенчмаркінгу, яка дозволяє маркувати тести, виконувати багато тестів і усереднювати час їх виконання, блок виклику, що викликається між тестами (тобто перемішування масиву між вимірюванням алгоритму сортування на ньому), чіткий друк бенчмаркінгу результати, а також повертає середній час виконання як Double.

Спробуйте наступне:

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

Використання

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

Примітка: measure також повертає час виконання. В label, testsі setupаргументи НЕ є обов'язковими. printResultsАргумент встановлений trueза замовчуванням.


3

Мені подобається відповідь Бреда Ларсона на простий тест, який ви навіть можете запустити на дитячому майданчику. Для власних потреб я трохи його підправив:

  1. Я обернув виклик функції, яку хочу перевірити, у функцію тестування, що дає мені простір, щоб пограти з різними аргументами, якщо захочу.
  2. Функція тестування повертає свою назву, тому мені не потрібно включати параметр 'title' у функцію averageTimeTo()порівняльного тестування.
  3. Функція порівняльного аналізу дозволяє за бажанням вказати, скільки повторень виконувати (за замовчуванням 10). Потім він повідомляє як загальний, так і середній час.
  4. Функція порівняльного тесту друкує на консолі І повертає averageTime(що можна очікувати від функції, яка називається 'averageTimeTo'), тому немає необхідності в двох окремих функціях, які мають майже однакову функціональність.

Наприклад:

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

1
Це досить розумно! І дякую за поділ. @discardableResultможе бути корисним, оскільки ви вже друкуєтеaverageTime
ielyamani

Так, хороший момент, це було б Swiftier, якщо у вас немає звички використовувати повернене значення (як показано в моєму прикладі!), Я навіть не помітив, оскільки воно не генерує попередження на ігрових майданчиках.
Кал

0

у вас є кілька можливостей

  • DispatchTime.now().uptimeNanoseconds (і div на 1_000_000)

    Виходячи з абсолютної одиниці часу Маха

Настінний годинник ( не рекомендується через стрибкову секунду або іншу мікрокорекцію)

  • Date().timeIntervalSince1970
  • CFAbsoluteTimeGetCurrent

Тест

  • XCTest
let metrics: [XCTMetric] = [XCTMemoryMetric(), XCTStorageMetric(), XCTClockMetric()]

let measureOptions = XCTMeasureOptions.default
measureOptions.iterationCount = 3

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

* Я б не пропонував вам створити загальну функцію за допомогою measure блоком, оскільки Xcode іноді не справляється з цим належним чином. Ось чому використовуйте measureблок для кожного тесту продуктивності

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.