Regarding arrays, I had another simple question …
That’s your idea of a simple question (-:
First things first, Swift arrays are (almost?) always allocated on the heap. The
Array
type itself is a struct, which is allocated on the stack, but the contents of the array is held in a heap-based buffer. It’s this indirection that allows for efficient copy on write (COW), appending and removing elements, and so on.
Swift has no direct equivalent of C’s fixed sized array. When the C importer sees a C fixed sized array, it imports it as an N-element tuple, which works but is super kludgy and not the way you’d write the code in Swift itself.
DSP-ish stuff isn’t really my thing, so I don’t have a lot of direct experience here, but if I were in your shoes I’d build an abstraction layer around these large buffers of floats so that you can explicitly manage their allocation, copying and destruction. If you’re explicitly trying to deal with mutable state (and thus avoid COW), you can make this a class and pass around a reference.
One of the nice things about Swift is that you can build abstraction layers like this without (necessarily) paying a runtime performance cost. Specifically, if you declare your class
final
then Swift can devirtualise, inline, and so on.
Here’s a very quick example of what I’m talking about:
final class FixedBuffer {
init() {
self.buffer = UnsafeMutablePointer<Float>.allocate(capacity: 1024 * 1024)
self.buffer.initialize(to: 0.0, count: 1024 * 1024)
}
deinit {
print("deinit")
self.buffer.deinitialize(count: 1024 * 1024)
self.buffer.deallocate(capacity: 1024 * 1024)
}
fileprivate var buffer: UnsafeMutablePointer<Float>
subscript(row: Int, column: Int) -> Float {
get {
precondition((0..<1024).contains(row))
precondition((0..<1024).contains(column))
return self.buffer[row * 1024 + column]
}
set {
precondition((0..<1024).contains(row))
precondition((0..<1024).contains(column))
self.buffer[row * 1024 + column] = newValue
}
}
}
func inner(b: FixedBuffer) {
print(">inner")
b[1, 1] = 1.0
print("<inner")
}
func outer() {
print(">outer")
let b = FixedBuffer()
print(b[0, 0])
print(b[1, 1])
inner(b: b)
print(b[0, 0])
print(b[1, 1])
print("<outer")
}
outer()
It prints:
>outer
0.0
0.0
>inner
<inner
0.0
1.0
<outer
deinit
Another nice thing about this approach is that you can build specific adapters for specific APIs. For example, if you need to call some vDSP function that takes a traditional C array of Floats, you can write a method that calls a closure with the parameters you need. For example:
extension FixedBuffer {
func with<Result>(row: Int, body: (_ rowBase: UnsafeMutablePointer<Float>, _ count: Int) throws -> Result) rethrows -> Result {
return try body(self.buffer + row * 1024, 1024)
}
}
which you can call like this:
b.with(row: 0) { (rowBase: UnsafeMutablePointer<Float>, count: Int) in
… call vDSP here …
}
Because the closure is non-escaping (the new default in Swift 3) this sort of thing can be really efficient.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"