Forked from RomanTruba/Synchronization_test_iOS_SDK10
Last active
August 29, 2023 08:47
-
-
Save steipete/36350a8a60693d440954b95ea6cbbafc to your computer and use it in GitHub Desktop.
Updated for Xcode 8, Swift 3; added os_unfair_lock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SpinlockTestTests.swift | |
// SpinlockTestTests | |
// | |
// Created by Peter Steinberger on 04/10/2016. | |
// Copyright © 2016 PSPDFKit GmbH. All rights reserved. | |
// | |
import XCTest | |
final class LockingTests: XCTestCase { | |
func testSpinLock() { | |
var spinLock = OS_SPINLOCK_INIT | |
executeLockTest { (block) in | |
OSSpinLockLock(&spinLock) | |
block() | |
OSSpinLockUnlock(&spinLock) | |
} | |
} | |
func testUnfairLock() { | |
var unfairLock = os_unfair_lock_s() | |
executeLockTest { (block) in | |
os_unfair_lock_lock(&unfairLock) | |
block() | |
os_unfair_lock_unlock(&unfairLock) | |
} | |
} | |
func testDispatchSemaphore() { | |
let sem = DispatchSemaphore(value: 1) | |
executeLockTest { (block) in | |
_ = sem.wait(timeout: DispatchTime.distantFuture) | |
block() | |
sem.signal() | |
} | |
} | |
func testNSLock() { | |
let lock = NSLock() | |
executeLockTest { (block) in | |
lock.lock() | |
block() | |
lock.unlock() | |
} | |
} | |
func testPthreadMutex() { | |
var mutex = pthread_mutex_t() | |
pthread_mutex_init(&mutex, nil) | |
executeLockTest{ (block) in | |
pthread_mutex_lock(&mutex) | |
block() | |
pthread_mutex_unlock(&mutex) | |
} | |
pthread_mutex_destroy(&mutex); | |
} | |
func testSyncronized() { | |
let obj = NSObject() | |
executeLockTest{ (block) in | |
objc_sync_enter(obj) | |
block() | |
objc_sync_exit(obj) | |
} | |
} | |
func testQueue() { | |
let lockQueue = DispatchQueue.init(label: "com.test.LockQueue") | |
executeLockTest{ (block) in | |
lockQueue.sync() { | |
block() | |
} | |
} | |
} | |
func disabled_testNoLock() { | |
executeLockTest { (block) in | |
block() | |
} | |
} | |
private func executeLockTest(performBlock:@escaping (_ block:() -> Void) -> Void) { | |
let dispatchBlockCount = 16 | |
let iterationCountPerBlock = 100_000 | |
// This is an example of a performance test case. | |
let queues = [ | |
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive), | |
DispatchQueue.global(qos: DispatchQoS.QoSClass.default), | |
DispatchQueue.global(qos: DispatchQoS.QoSClass.utility), | |
] | |
var value = 0 | |
self.measure { | |
let group = DispatchGroup.init() | |
for block in 0..<dispatchBlockCount { | |
group.enter() | |
let queue = queues[block % queues.count] | |
queue.async(execute: { | |
for _ in 0..<iterationCountPerBlock { | |
performBlock({ | |
value = value + 2 | |
value = value - 1 | |
}) | |
} | |
group.leave() | |
}) | |
} | |
_ = group.wait(timeout: DispatchTime.distantFuture) | |
} | |
} | |
} |
Don't really understand any of this. Anyway to break it down in sinple was? Been trying to figure it out for months now but still I am lost. Thanks for your help.
I won't recommend to use os_unfair_lock_s as in this code, PLEASE, read this before http://www.russbishop.net/the-law.
In short, in swift you must use
var unfairLock: UnsafeMutablePointer<os_unfair_lock>
...
init
unfairLock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
unfairLock.initialize(to: os_unfair_lock())
deinit
unfairLock.deallocate()
instead of
var unfairLock = os_unfair_lock_s()
...
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@drewster99 I ran a similar test and it seems, NSLock, pthread_mutex and os_unfair_lock all are pretty close and much faster than these test results above suggest. This difference is probably the effect of calling a block in the inner loop of Peter's test, which I do not in my test case (in order to solely measure the effect of the locks).
While I currently have a top notch MacBook, my results are roughly 0.16 secs for 16 queues and a loop counter of 100_000 - for incrementing a shared counter. I didn't test DispatchSemaphore because, well ... I was lazy and it shouldn't be used in those scenarios. ;)
What's interesting though, that in the case of os_unfair_lock, Swift adds an overhead (the usual refcounting management) counting to roughly 70%, and 30% is actually the time spend in os_unfair_lock. So, my guess is, C/C++/Obj-C should be about 3 times faster using os_unfair_lock. It might be even more faster using raw std::atomics, which however cannot be directly compared, because the exact usage of std::atomics depends on what you are synchronising.
Another observation is, that the uncontended case is roughly 4 times faster (i.e., 16 x 100_000 ops on a single queue).
And omitting the function call to os_unfair_lock (i.e. making it inline), would Swift enable to omit a +ref and -ref counter op per lock/unlock pair, which would make the operation much more faster. Well, the function call to os_unfair_lock is not inlineable :/