关于性能:如何对Swift代码执行进行基准测试?

How to benchmark Swift code execution?

是否有一种方法/软件提供执行用swift编写的代码块所需的精确时间,但以下情况除外?

1
2
3
4
5
let date_start = NSDate()

// Code to be executed

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


如果您只需要一个代码块的独立计时功能,我使用以下快捷助手功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
}

前者将记录给定代码段所需的时间,后者将其作为浮点返回。作为第一个变体的例子:

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

将注销如下内容:

Time elapsed for map(): 0.0617449879646301 s

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


如果您想深入了解某个代码块的性能,并确保在进行编辑时性能不会受到影响,最好是使用XCEST的度量性能函数,如measure(_ block: () -> Void)

编写一个单元测试来执行您想要基准测试的方法,该单元测试将多次运行它,给您所需的时间和结果的偏差。

1
2
3
4
5
6
func testExample() {

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

您可以在Apple文档中找到更多信息,这些文档正在使用xcode->性能测试进行测试。


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

1
2
3
4
5
6
7
8
9
10
11
import Foundation

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

    let startTime = CFAbsoluteTimeGetCurrent()

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

所以基本上你传递一个接受函数作为参数的块,它用来告诉度量什么时候结束。

例如,要测量某个名为"myAsyncCall"的调用需要多长时间,您可以这样调用它:

1
2
3
4
5
6
measure("some title") { finish in
    myAsyncCall {
        finish()
    }
    // ...
}

对于同步代码:

1
2
3
4
5
measure("some title") { finish in
     // code to benchmark
     finish()
     // ...
}

这应该类似于XCEST中的MeasureBlock,尽管我不知道它在那里的实现方式。


基准功能-Swift 4.2

这是一个非常通用的基准测试功能,允许对测试进行标记、执行许多测试并平均执行时间、在测试之间调用设置块(即在测试上测量排序算法之间切换数组)、清晰打印基准测试结果,还返回平均执行时间。我是一个江户。

请尝试以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@_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)
")
        } else {
            print("Execution time: \(avgTimeStr)s")
            print("Number of tests: \(tests)
")
        }
    }

    return avgExecutionTime
}

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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


我喜欢布拉德·拉森对一个简单测试的回答,你甚至可以在操场上跑步。为了我自己的需要,我做了一些调整:

  • 我已经将对要测试的函数的调用包装在一个测试函数中,如果我愿意,它为我提供了使用不同参数的空间。
  • 测试函数返回其名称,因此我不必在averageTimeTo()基准函数中包含'title'参数。
  • 基准功能允许您选择指定要执行的重复次数(默认值为10)。然后它报告总时间和平均时间。
  • Benchmarking函数将打印到控制台并返回averageTime(您可能会从名为'AverageTimeTo'的函数中得到),因此不需要两个功能几乎相同的独立函数。
  • 例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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
    ")
        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