Hi, I wanted to write some code to test when async/await is faster than DispatchQueue.
I asked myself two questions:
- Is adding new Job to an Executor faster than adding new Work Item to DispatchQueue (including executing time)
- Is continuations switching in Async/Await faster than thread switching in DispatchQueue
For first question I have created simple functions like arithmetic calculations or just empty function and execute them concurrently in TaskGroup/DispatchQueue.
For second question I have used the same functions from first test but add some Task.sleep/usleep to force continutaions/thread switching
First test showed that DispatchQueue is faster, second showed no visible differences, sometimes one is faster, sometimes the other.
As far as I know after watching WWDCs and reading Swift proposals at least second test should show how Async/Await continuations switching is faster than thread switching in DispatchQueue.
Am I missing something or maybe my test methods are incorrect?
I'm attaching me code below, thanks! :)
class ViewController: UIViewController {
var stoper = Stoper()
var queue = DispatchQueue(label: "queue", attributes: .concurrent)
let iterations: Int = 500
override func viewDidLoad() {
super.viewDidLoad()
// queueOperation(title: "LONG COMPUTATIONS", longComputations)
// asyncOperation(title: "LONG COMPUTATIONS", longComputationsAsync)
// queueOperation(title: "SHORT COMPUTATIONS", shortComputations)
// asyncOperation(title: "SHORT COMPUTATIONS", shortComputationsAsync)
// queueOperation(title: "EMPTY", empty)
// asyncOperation(title: "EMPTY", emptyAsync)
// queueOperation(title: "EMPTY WITH SLEEP", emptyWithSleep)
// asyncOperation(title: "EMPTY WITH SLEEP", emptyWithSleepAsync)
queueOperation(title: "LONG COMPUTATIONS WITH SLEEP", longComputationsWithSleep)
asyncOperation(title: "LONG COMPUTATIONS WITH SLEEP", longComputationsWithSleepAsync)
}
/// Execute 'operation' concurrently on DispatchQueue
func queueOperation(title: String, _ operation: @escaping (@escaping () -> Void) -> Void) {
print("Start dispatch \(title)")
// Start timer
stoper.start()
let group = DispatchGroup()
for _ in 0..<iterations {
group.enter()
queue.async {
operation {
group.leave()
}
}
}
group.wait()
// End timer
print("Time \(title): \(stoper.stop())\n")
}
/// Execute 'operation' concurrently on TaskGroup
func asyncOperation(title: String, _ operation: @escaping () async -> Void) {
print("Start async \(title)")
// Start timer
stoper.start()
Task.detached {
await withTaskGroup(of: Void.self) { task in
for _ in 0..<self.iterations {
task.addTask {
let _ = await operation()
}
}
await task.waitForAll()
// End timer
print("Time \(title): \(await self.stoper.stop())")
}
}
}
// >>>>>>>>>> LONG COMPUTATIONS <<<<<<<<<< //
func longComputationsAsync() async {
var value: Double = 0.1
for _ in 0..<10000 { value /= 0.1 }
for _ in 0..<10000 { value *= 0.1 }
}
func longComputations(callback: @escaping () -> Void) {
var value: Double = 0.1
for _ in 0..<10000 { value /= 0.1 }
for _ in 0..<10000 { value *= 0.1 }
callback()
}
// >>>>>>>>>> LONG COMPUTATIONS WITH SLEEP <<<<<<<<<< //
func longComputationsWithSleepAsync() async {
var value: Double = 0.1
for _ in 0..<10 {
for _ in 0..<1000 { value /= 0.1 }
for _ in 0..<1000 { value *= 0.1 }
try! await Task.sleep(nanoseconds: 1000_000)
}
}
func longComputationsWithSleep(callback: @escaping () -> Void) {
var value: Double = 0.1
for _ in 0..<10 {
for _ in 0..<1000 { value /= 0.1 }
for _ in 0..<1000 { value *= 0.1 }
usleep(1000)
}
callback()
}
// >>>>>>>>>> SHORT COMPUTATIONS <<<<<<<<<< //
func shortComputationsAsync() async {
var value: Double = 0.1
for _ in 0..<10 { value /= 0.1 }
for _ in 0..<10 { value *= 0.1 }
}
func shortComputations(callback: @escaping () -> Void) {
var value: Double = 0.1
for _ in 0..<10 { value /= 0.1 }
for _ in 0..<10 { value *= 0.1 }
callback()
}
// >>>>>>>>>> EMPTY <<<<<<<<<< //
func emptyAsync() async {
}
func empty(callback: @escaping () -> Void) {
callback()
}
// >>>>>>>>>> EMPTY WITH SLEEP <<<<<<<<<< //
func emptyWithSleepAsync() async {
try! await Task.sleep(nanoseconds: 1000_000)
}
func emptyWithSleep(callback: @escaping () -> Void) {
usleep(1000)
callback()
}
}
struct Stoper {
var startTime: DispatchTime = .init(uptimeNanoseconds: 0)
mutating func start() {
startTime = DispatchTime.now()
}
func stop() -> Double {
let stopTime = DispatchTime.now()
let nanoTime = stopTime.uptimeNanoseconds - startTime.uptimeNanoseconds
let timeInterval = Double(nanoTime) / 1_000_000_000
return timeInterval
}
}