【问题标题】:How to benchmark Swift code execution?如何对 Swift 代码执行进行基准测试?
【发布时间】:2014-09-20 06:56:46
【问题描述】:

除了以下代码之外,有没有一种方法/软件可以给出执行一段用 Swift 编写的代码块所需的精确时间?

let date_start = NSDate()

// Code to be executed 

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

【问题讨论】:

  • ...您的意思是除了使用仪器之外?你真的想为自己得到这些东西吗?作为一个人,您不仅想了解它,还需要您的代码来解决它?

标签: swift performance


【解决方案1】:

@Brad Larsons 的答案(接受的答案)的小改进,以允许函数在需要时返回操作结果

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

【讨论】:

    【解决方案2】:
    class TimeMeasurement {
    static var shared: TimeMeasurement = TimeMeasurement()
    private var start = CFAbsoluteTimeGetCurrent()
    func begin() {
        start = CFAbsoluteTimeGetCurrent()
    }
    @discardableResult func end() -> Double {
        let diff = CFAbsoluteTimeGetCurrent() - start
        print("Took \(diff) seconds")
        return diff
    }
    

    }

    【讨论】:

      【解决方案3】:

      你有几种可能

      • DispatchTime.now().uptimeNanoseconds(和 1_000_000 上的 div)

        基于马赫绝对时间单位

      挂钟(不推荐因为闰秒或其他微修正)

      • 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

      【讨论】:

        【解决方案4】:

        您可以使用此功能来测量异步以及同步代码:

        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,虽然我不知道它是如何在那里实现的。

        【讨论】:

        • 需要@escaping
        【解决方案5】:

        基准测试功能 - 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

        【讨论】:

          【解决方案6】:

          我喜欢 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
          

          【讨论】:

          • 这很聪明!并感谢您的分享。 @discardableResult 可能很有用,因为您已经在打印 averageTime
          • 是的,好点,如果你不习惯使用返回值(如我的示例所示!)那将是 Swiftier,我什至没有注意到,因为它不会生成操场上的警告。
          【解决方案7】:

          如果您想深入了解某个代码块的性能并确保在进行编辑时不会影响性能,最好的办法是使用XCTest's 测量性能函数,例如measure(_ block: () -&gt; Void)

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

          func testExample() {
          
              self.measure {
                  //do something you want to measure
              }
          }
          

          您可以在Testing with Xcode -> Performance Testing 下的苹果文档中找到更多信息

          【讨论】:

          • 请注意measureBlock 将多次执行块代码以获得更好的统计数据。
          • 什么是measureBlock?它来自哪里(我应该导入什么)?在 Swift 4 中
          • measureBlock() 是 XCTestCase 上的一个方法。导入 XCTest... 文件 -> 新建 -> 测试用例模板将默认添加该块
          【解决方案8】:

          如果您只想要一个独立的代码块计时函数,我使用以下 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 benchmarks will vary heavily based on the level of optimization you select,因此这可能仅对 Swift 执行时间的相对比较有用。即使这可能会在每个 beta 版本的基础上发生变化。

          【讨论】:

          • 谢谢你的回答很有帮助
          • 这种技术的一个问题是它引入了一个新的范围,因此在该范围内声明的任何内容都将在它之外不可用(例如,在您的示例中,resultArray1 将不可用于后面的代码关闭。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-11-25
          • 2013-03-10
          • 2011-01-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-15
          相关资源
          最近更新 更多