Hi,
Thanks for your detailed answer. It feels like OSAtomicTestAndSet is exactly the fit I'm after, unfortunately. Which is check this flag and only run if it's not set, so 'mostly don't execute anything, and don't block while you're at it.' It seems most Swift built-ins are of the blocking variety, designed to execute multiple tasks safely. It may (or may not) be a subtle difference, but I want to not execute multiple tasks safely. 🙂
So, I tried out NSlock.try() which although non-blocking, seems to introduce a potential negative side effect depending on which thread the lock executes on. And then I tried a queue implementation, which feels better (than NSLock). I use two queues to avoid blocking while actually processing the frame, but want to 'release' the variable on the external video frame queue.
So I was thinking I'll go with the double-queue implementation. I'm running at 30fps (video) at the moment, so the time difference from OSAtomic shouldn't really have an impact. I added in a little check to see if any iterations were being skipped in the queue version, and was a little surprised that more than 50% were being skipped due to the time taken for processingFrame=0 to finally execute. I'll have to run this on a device (iPhone) and see what effect there is when trying to process video frames.
I haven't really gone into mixing C and Swift before (as an abstraction around processingFrame), but perhaps that's where I'm headed in the longer term. Although at that point I guess I'd just be re-implementing OSAtomicTestAndSet.
/ /: Playground - noun: a place where people can play
import Foundation
// simulated external queue, this is really the AVCaptureSession delegate queue
let captureSessionFrameQueue = DispatchQueue(label: "my_video_frame_q")
func atomic(count: Int)->Double {
var processingFrame = 0
let start_time = Date()
for _ in 0...count {
if(OSAtomicTestAndSet(0, &processingFrame) == false) {
processingFrame = 0
}
}
let time_taken = Date().timeIntervalSince(start_time)
return time_taken
}
func locking(count: Int)->Double {
let myLock = NSLock()
let start_time = Date()
for _ in 0...count {
if (myLock.try()) {
myLock.unlock()
}
}
let time_taken = Date().timeIntervalSince(start_time)
return time_taken
}
func serial_queue(count: Int)->Double {
let mySerialQueue = DispatchQueue(label: "my_serial_q")
let myFrameProcessingQueue = DispatchQueue(label: "my_frame_processing_q")
var processingFrame = 0
var ignored = 0
let start_time = Date()
for _ in 0...count {
mySerialQueue.async {
if processingFrame == 0 {
processingFrame = 1
myFrameProcessingQueue.async {
// do work on current video frame, but don't block mySerialQueue
// must 'release' processingFrame on captureSessionFrameQueue
// so it executes after any currently queued video frames
captureSessionFrameQueue.async(execute: {
processingFrame = 0
})
}
}
else {
ignored += 1
}
}
}
let time_taken = Date().timeIntervalSince(start_time)
print("serial_queue processed \(count-ignored) and ignored \(ignored)")
return time_taken
}
let iterations = 100000
for _ in 1...5 {
print(atomic(count: iterations), locking(count: iterations), serial_queue(count: iterations))
}
results:
serial_queue processed 41581 and ignored 58365
5.79971098899841 15.9877420067787 31.2861160039902
serial_queue processed 41906 and ignored 58095
5.50851899385452 16.1453329920769 32.2434309720993
serial_queue processed 41583 and ignored 58417
5.50454598665237 15.3047040104866 31.2974920272827
serial_queue processed 41883 and ignored 58118
5.82396399974823 15.4081450104713 28.7865089774132
serial_queue processed 43892 and ignored 56108
5.36022198200226 13.5767689943314 29.1582999825478
cheers,