Search code examples
arraysswiftpointersinteropmutability

Passing a reference to an element of an array to Accelerate


One of the annoyances with the Swift language is its endlessly mutating syntax (and the syntax converter's unreliability). That, and the language designer's fear of pointers.

I've been working with large arrays of audio data. Naturally, I also use Apple's rather nice Accelerate framework for bread and butter operations such as FFTs, windowing functions, and the like.

If I have an array something like this:

var veryBigArray = Float[repeating: 0.0, count: 1024 * 1024]

and I want to do a windowing operation + an FFT on 1024 elements, starting at element n, it turns out to be astonishingly tricky to do. A reference to the nth element causes the compiler to copy the nth element to someplace else, and then give me a reference to the new temporary location. This is, of course, completely useless to me. This is presumably because a reference could lead to a change in the element's content, and thus trigger a copy-on-write (I think that this is taking the fear of mutability way past what's reasonable - classes already have this kind of mutability, and the sky hasn't fallen).

I could use:

Array(veryBigArray[n ..< n+1024])

but this results in a horrible copy operation, and if done in a loop, quickly brings the machine to its knees. I could pass a slice, but I have no idea of what the rules might be for any undocumented copy operations that might take place.

I had actually started to migrate this to C, when I thought that perhaps I could use something like this:

    var basePtr = UnsafeMutableBufferPointer(start: &veryBigArray, count: veryBigArray.count)
    let ptr = UnsafePointer<Float>(basePtr.baseAddress)
    var ptr1 = ptr! + n

Now I can call the Accelerate functions. For example:

    vDSP_sve(ptr1, 1, &aResultFloat, vDSP_Length(1024))

This actually works, and might even be my best bet (given that I have a huge investment in Swift, even though using C would have been a FAR more rational option). The downside is mostly that it's clunky, and the pointers are unowned, which precludes using ARC.

Expanding a bit, what I do is to read an audiobuffer from a resource. I'd like to have an owned pointer to the buffer which I can null out when I want ARC to dispose of the buffer.

Here's how I read the resource:

func loadAudioFile(fileName: String) -> (UnsafePointer<Float>?, Int) {
//  let audioSession = AVAudioSession.sharedInstance()

let fileURL = Bundle.main.url(forResource: fileName, withExtension: "aiff")
if fileURL == nil {return (nil, 0) }
if let audioFile = try? AVAudioFile(forReading: fileURL!) {
    let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: UInt32(audioFile.length))
    if ((try? audioFile.read(into: buffer)) != nil) {
        return (UnsafePointer(buffer.floatChannelData?.pointee), Int(audioFile.length))
    }
}
return (nil, 0)

}

But I wonder if there might be a better option?

ContiguousArray is appealing in principal, but it's essentially undocumented, and not particularly inter-operable with regular arrays. It also suffers from what seems like an incredibly silly decision to not allow address references to elements of the array (they are handled in the same silly way as with regular arrays).

I could pass UnsafeMutableBufferPointers back and forth around my system. If I give this tongue-twister a typealias (which has since been renamed to associatedtype, let anyone actually guess what it's for), it might not be completely horrible ... and it has the benefit of being guaranteed contiguous. But it seems even more unwieldy than bridging to obj-c to do the heavy lifting.

I have to confess to being frustrated with what seems to me to be a very simple, and very common operation, that is almost impossible to implement in Swift.

The best thing I've seen so far was posted by Chris Liscio a couple years ago. Of course, since he posted it, the syntax has changed, and the syntax converter doesn't work on his code, but I could certainly take it as a starting point, and build something that serves my needs.

I was really excited when Swift was announced. The language seemed to be well designed, and even compiled to native code, which was important to me. It was supposed to be inter-operable with C. So I made a major commitment to it.

But the last two years have been marked by endless frustration with flaky compilers, syntax that seemingly changes with every beta version, and apparent hostility on the part of the core Swift team toward people who actually want to use their toy language to do real work (which is why I see no point to raising a language proposal that is guaranteed to go nowhere).

Does anyone have a better way to approach the problem of passing the address of a location that is inside a big audio buffer to the Accelerate functions? With every passing year, I find that there are more smart people who have figured out better ways to do things than I have, so I'd love to hear from anyone who has a decent solution. Or even an idea that might point to a good solution.


Solution

  • You are on the right track with UnsafeMutableBufferPointer, but there is (at least theoretically) a problem: According to the documentation

    var basePtr = UnsafeMutableBufferPointer(start: &veryBigArray, count: veryBigArray.count)
    

    passes a pointer to the start of the array which is "life-time extended for the duration of the call". Which means that the pointer is not guaranteed to be valid after the UnsafeMutableBufferPointer constructor returns.

    Also there is no need for a mutable pointer because vDSP_sve() does not modify the given array.

    The (in my opinion) correct solution is

    veryBigArray.withUnsafeBufferPointer {
        vDSP_sve($0.baseAddress! + n, 1, &aResultFloat, vDSP_Length(1024))
    }
    

    which passes a pointer to the array's contiguous storage to the closure. If no such storage exists, it is first created.

    There is also withUnsafeMutableBufferPointer() if you need a pointer to the array's mutable contiguous storage.