I have a Swift app running on macOS. It batch-processes images. I don't know ahead of time how big these images are going to be, and what hardware my app is going to run on - these are both user dependent.
I use GCD to parallelize the processing of the images as it can really accelerate the throughput.
However, in some cases, too much parallelization can hurt: if the user processes high resolution images, the parallelization creates too much memory pressure, and the system's performance becomes really poor.
So I'd like to find a way to "feed my parallel task processor" at a rate that maximizes parallelization while keeping the workload in RAM (so there aren't any triggers to paging & swapping: I want to avoid disk IO).
Any ideas on how to do that?
I ended up implementing a TokenBucket-type of singleton that deals with admission control based on memory requirements. It gets initialized such that 80% of the RAM can be used by my app.
let memoryGate = MemoryGate(maxBytes: ProcessInfo.processInfo.physicalMemory*8/10)
When someone wants to perform a memory intensive operation, it has to request() memory from it. If there isn't enough memory, the call blocks until there is. After being done, the thread has to release() the memory.
Code:
class MemoryGate {
private let maxBytes : UInt64
private var availableBytes : Int64
private let cv = NSCondition()
init(maxBytes: UInt64) {
self.maxBytes = maxBytes
self.availableBytes = Int64(maxBytes)
Log.debug?.message("maxBytes=\(maxBytes)")
}
public func request(amount: UInt64) {
Log.debug?.message("Resquesting \(amount) bytes")
cv.lock()
// If the amount is bigger than the max allowed, no amount of waiting is going
// to help, so we go through and let the other smaller jobs be held back until
// memory is freed
if (amount <= maxBytes) {
while (availableBytes < Int64(amount)) {
cv.wait()
}
}
availableBytes -= Int64(amount)
Log.debug?.message("Got \(amount) bytes. availableBytes=\(availableBytes)")
cv.unlock()
}
public func release(amount: UInt64) {
cv.lock()
availableBytes += Int64(amount)
Log.debug?.message("Released \(amount) bytes. availableBytes=\(availableBytes)")
cv.broadcast()
cv.unlock()
}
}
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments