如何对Swift代码执行进行基准测试?


77

除了以下内容之外,是否有其他方法/软件可以给出执行用Swift编写的代码块所需的准确时间?

let date_start = NSDate()

// Code to be executed 

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

1
...您的意思不是使用乐器?您实际上是想自己买这个东西吗?您不只是想作为一个人来了解它,还需要您的代码才能实现它?
Tommy

Answers:


135

如果您只想为代码块提供独立的计时功能,则可以使用以下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) }
}

将注销类似:

map()经过的时间:0.0617449879646301 s

请注意,Swift基准测试会根据您选择的优化级别而有很大不同,因此这可能仅对Swift执行时间的相对比较有用。即使这样,也可能会在每个beta版本的基础上发生变化。


3
谢谢您的回答非常有帮助
ielyamani 2014年

这种技术的一个问题是它引入了一个新的作用域,因此在该作用域内声明的任何内容都将无法在其外部使用(例如,在您的示例中,resultArray1在关闭后将无法进行编码。)
pbouf77

36

如果您想深入了解特定代码块的性能,并确保在进行编辑时性能不会受到损害,最好的方法是使用XCTest的测量性能函数,例如measure(_ block: () -> Void)

编写一个执行您要进行基准测试的方法的单元测试,该单元测试将多次运行它,从而为您提供所需的时间和结果偏差

func testExample() {

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

您可以在“使用Xcode测试->性能测试”下的Apple文档中找到更多信息。


6
请注意,measureBlock将多次执行该块代码以获得更好的统计信息。
Eneko Alonso

是什么measureBlock?它来自哪里(我应该从哪里进口)?在Swift 4中
user924 '18

measureBlock()是XCTestCase上的一种方法。导入XCTest ...文件->新建->测试用例模板将默认添加该块
kviksilver

16

您可以使用此函数来测量异步代码和同步代码:

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()
     // ...
}

这应该类似于XCTest中的measureBlock,尽管我不知道它在那里是如何实现的。


@需要转义
Cameron Lowell Palmer

8

基准测试功能-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还返回执行时间。的labeltestssetup参数都是可选的。该printResults参数true默认设置为。


3

我喜欢布拉德·拉尔森(Brad Larson)给出的简单测试答案,您甚至可以在Playground中运行它。为了我自己的需要,我对其进行了一些调整:

  1. 我已经将对要测试的函数的调用包装在测试函数中,这为我提供了在需要时可以使用不同参数的空间。
  2. 测试函数返回其名称,因此我不必在averageTimeTo()基准测试函数中包括“ title”参数。
  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 '18

是的,如果您不习惯使用返回值(如我的示例所示!)的习惯,那会更快捷,我什至没有注意到,因为它不会在操场上产生警告。
卡尔

0

你有几种可能性

  • CFAbsoluteTimeGetCurrent
  • Date().timeIntervalSince1970
  • DispatchTime.now().uptimeNanoseconds (以及div在1_000_000上)
  • XC测试
let metrics: [XCTMetric] = [XCTMemoryMetric(), XCTStorageMetric(), XCTClockMetric()]

let measureOptions = XCTMeasureOptions.default
measureOptions.iterationCount = 3

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

*我不建议您使用measureblock创建通用函数,因为Xcode有时无法正确处理它。这就是为什么measure对每个性能测试都使用块

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.