How to benchmark Swift code execution?
If you just want a standalone timing function for a block of code, I use the following Swift helper functions:
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)
}
The former will log out the time required for a given section of code, with the latter returning that as a float. As an example of the first variant:
printTimeElapsedWhenRunningCode(title:"map()") {
let resultArray1 = randoms.map { pow(sin(CGFloat($0)), 10.0) }
}
will log out something like:
Time elapsed for map(): 0.0617449879646301 s
Be aware that Swift benchmarks will vary heavily based on the level of optimization you select, so this may only be useful for relative comparisons of Swift execution time. Even that may change on a per-beta-version basis.
You can use this function to measure asynchronous as well as synchronous code:
import Foundation
func measure(_ title: String, block: (@escaping () -> ()) -> ()) {
let startTime = CFAbsoluteTimeGetCurrent()
block {
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("\(title):: Time: \(timeElapsed)")
}
}
So basically you pass a block that accepts a function as a parameter, which you use to tell measure when to finish.
For example to measure how long some call called "myAsyncCall" takes, you would call it like this:
measure("some title") { finish in
myAsyncCall {
finish()
}
// ...
}
For synchronous code:
measure("some title") { finish in
// code to benchmark
finish()
// ...
}
This should be similar to measureBlock from XCTest, though I don't know how exactly it's implemented there.
If you want to get insight into performance of a certain block of code and make sure performance doesn't hurt when you make edits, best thing would be using XCTest's measuring performance functions, like measure(_ block: () -> Void)
.
Write a unit test that executes method you want to benchmark, and that unit test will run it multiple times giving you time needed and deviation of results
func testExample() {
self.measure {
//do something you want to measure
}
}
You can find more info in apple docs under Testing with Xcode -> Performance Testing
Benchmarking Function - Swift 4.2
This is an incredibly versatile benchmarking function that allows for the labelling of tests, performing many tests and averaging their execution times, a setup block to be called between tests (i.e. shuffling an array between measuring a sorting algorithm on it), clear printing of benchmarking results, and it also returns the average execution time as a Double
.
Try the following:
@_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
}
Usage
var arr = Array(1...1000).shuffled()
measure(label: "Map to String") {
let _ = arr.map { String($0) }
}
measure(label: "Apple's Sorting Method", 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
//
Note: measure
also returns the execution time. The label
, tests
, and setup
arguments are optional. The printResults
argument is set to true
by default.