Skip to content

Instantly share code, notes, and snippets.

@nicklockwood
Created December 1, 2017 11:04
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicklockwood/a99f73c22cac4249a8e9ba2fdb93e638 to your computer and use it in GitHub Desktop.
Save nicklockwood/a99f73c22cac4249a8e9ba2fdb93e638 to your computer and use it in GitHub Desktop.
Thread-safe cache implementation benchmark
var cache = [Int: Int]()
let queue = DispatchQueue(label: "cacheQueue", attributes: .concurrent)
let iterations = 100000
// In the first run, cache is empty so we're writing each time
do {
let start = CFAbsoluteTimeGetCurrent()
for i in 0 ... iterations {
var exists = false
queue.sync {
if cache[i] != nil {
exists = true
}
}
if !exists {
queue.async(flags: .barrier) {
cache[i] = i
}
}
}
queue.sync {
print("read + write:", CFAbsoluteTimeGetCurrent() - start)
}
}
// In the second run, cache is full so we're only reading
do {
let start = CFAbsoluteTimeGetCurrent()
for i in 0 ... iterations {
var exists = false
queue.sync {
if cache[i] != nil {
exists = true
}
}
if !exists {
preconditionFailure()
}
}
queue.sync {
print("reading only:", CFAbsoluteTimeGetCurrent() - start)
}
}
// serial queue, sync read + write,
// read + write: 0.0808299779891968
// reading only: 0.0366910099983215
// serial queue, sync read, async write
// read + write: 1.15412402153015
// reading only: 0.0387730002403259
// concurrent queue, sync read, async write with barrier
// read + write: 1.31403398513794
// reading only: 0.0410760045051575
@nicklockwood
Copy link
Author

nicklockwood commented Dec 1, 2017

Update: it's even faster to use os_unfair_lock instead of DispatchQueue:

var cache = [Int: Int]()
let iterations = 100000

let lock: os_unfair_lock_t = .allocate(capacity: 1)
lock.initialize(to: os_unfair_lock())
defer {
    lock.deinitialize()
    lock.deallocate(capacity: 1)
}

// In the first run, cache is empty so we're writing each time
do {
    let start = CFAbsoluteTimeGetCurrent()
    for i in 0 ... iterations {
        var exists = false
        os_unfair_lock_lock(lock)
        if cache[i] != nil {
            exists = true
        }
        os_unfair_lock_unlock(lock)
        if !exists {
            os_unfair_lock_lock(lock)
            cache[i] = i
            os_unfair_lock_unlock(lock)
        }
    }
    print("read + write:", CFAbsoluteTimeGetCurrent() - start)
}

// In the second run, cache is full so we're only reading
do {
    let start = CFAbsoluteTimeGetCurrent()
    for i in 0 ... iterations {
        var exists = false
        os_unfair_lock_lock(lock)
        if cache[i] != nil {
            exists = true
        }
        os_unfair_lock_unlock(lock)
        if !exists {
            preconditionFailure()
        }
    }
    print("reading only:", CFAbsoluteTimeGetCurrent() - start)
}

// unfair lock (fastest)
// read + write: 0.0185399651527405
// reading only: 0.00243097543716431

// serial queue, sync read + write,
// read + write: 0.0808299779891968
// reading only: 0.0366910099983215

// serial queue, sync read, async write
// read + write: 1.15412402153015
// reading only: 0.0387730002403259

// concurrent queue, sync read, async write with barrier
// read + write: 1.31403398513794
// reading only: 0.0410760045051575

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment