I am trying to improve the performance of my cellular automata lab. I have two arrays of Double
s representing the current values and the next values.
If I run the calculation in a single thread I get about 28 steps per second. However, if I break the work down into 2, 3 or 4 chunks and pass them into a concurrency queue I still get about 28 steps per second. If I increase the chunks further the algorithm takes longer and longer to complete, for example 10 chunks drops the performance to about 10 steps per second.
I'm testing this on a 3rd generation iPad Pro with 4 performance cores and 4 efficiency cores.
func step(from: Int, to: Int) {
for j in from..<to {
for i in 0..<w {
AEMemoryClear(memory);
AEMemorySetValue(memory, sI, cells[i + (j )*w])
AEMemorySetValue(memory, aI, i != 0 && j != 0 ? cells[i-1 + (j-1)*w] : 0)
AEMemorySetValue(memory, bI, j != 0 ? cells[i + (j-1)*w] : 0)
AEMemorySetValue(memory, cI, i != wB && j != 0 ? cells[i+1 + (j-1)*w] : 0)
AEMemorySetValue(memory, dI, i != wB ? cells[i+1 + (j )*w] : 0)
AEMemorySetValue(memory, eI, i != wB && j != hB ? cells[i+1 + (j+1)*w] : 0)
AEMemorySetValue(memory, fI, j != hB ? cells[i + (j+1)*w] : 0)
AEMemorySetValue(memory, gI, i != 0 && j != hB ? cells[i-1 + (j+1)*w] : 0)
AEMemorySetValue(memory, hI, i != 0 ? cells[i-1 + (j )*w] : 0)
AERecipeExecute(recipe, memory)
next[i + j*w] = memory.pointee.slots[index].obj.a.x
}
}
}
func step() {
let start = DispatchTime.now()
let n: Int = 4
let z: Int = h/n
let group = DispatchGroup()
for i in 0..<n {
group.enter()
DispatchQueue.global(qos: .userInteractive).async { [unowned self] in
self.step(from: i*z, to: i == n-1 ? self.h : (i+1)*z)
group.leave()
}
}
group.notify(queue: .main) { [unowned self] in
(self.cells, self.next) = (self.next, self.cells)
let end = DispatchTime.now()
let delta = Double(end.uptimeNanoseconds - start.uptimeNanoseconds)/1000000000
let target: Double = 1.0/60
print("Time to calculate: \(delta) or \(round(1/delta)) SPS which is \(round(delta/target*100*10)/10)% of target; # of cells: \(self.w)^2 = \(self.w*self.h); seconds per cell: \(delta/Double(self.w*self.w))")
}
group.wait()
}
Also, another weird thing I'm noticing: if I run the calculation once a second it takes more than twice as long to complete than if I run it several times a second. The only reason I can possibly think is that its using the efficiency core instead of the performance core in that case.
Note: AEMemorySetValue
, AERecipeExecute
, AEMemoryClear
are C functions.
h
and w
are the cell dimensions of the cellular automata; the height and width. In practice they are the same and are about 300-500, depending on the device. Also, h
, w
, index
, sI
, aI
...hI
are all static values that do not change at all through this entire process.
I also entirely moved the inner step function from Swift to C, but that had zero effect on the performance, positive or negative.