// MARK: Mixing Swift and Accelerate. And generics.
• Chris Liscio
• Chris Liscio
When I first built my SMUGMath-Swift project back around WWDC time, I kept getting frustrated by the impedance mismatch when trying to move data between Swift and Accelerate.
Furthermore, I kept finding myself working to create a suitable collection of generic operations to resolve the mixture of [Float]
s, [Double]
s, and their associated Slice
variants.
Recently while implementing some nifty new stuff for FuzzMeasure 4, I had an idea to attempt some acrobatics (for me, anyway) with generics so that I could solve this same problem for a different reason. After spending a while in a Playground, I managed to get a generic vDSP multiply routine written that could work on both Slice<Float>
and [Float]
.
For no good reason at all, I decided that I should brain-dump this approach into my SMUGMath-Swift project before I lose the playground to the sands of time.
The “magic” is in the way that I was able to lean on the withUnsafeBufferPointer
method that exists for both collection types, and then also specify some additional type clauses to ensure we're working with Float
vs Double
floating point types.
Here's how it works.
public protocol Unsafeable : MutableCollectionType {
func withUnsafeBufferPointer<R>(body: (UnsafeBufferPointer<Self.Generator.Element>) -> R) -> R
mutating func withUnsafeMutableBufferPointer<R>(body: (inout UnsafeMutableBufferPointer<Self.Generator.Element>) -> R) -> R
}
extension Array : Unsafeable {}
extension Slice : Unsafeable {}
This is nothing too special or surprising. I'm not sure if it was a recent change in Swift, but I wasn't able to get away with specifying Self.Generator.Element
on the UnsafeMutableBufferPointer
type when I originally did this. The above used to be slightly more ugly, but I was able to get away with this in Xcode 6.3b1.
public func mul<C: Unsafeable where C.Generator.Element == Float, C.Index == Int>( var x: C, y: C ) -> [Float] {
assert( count(x) == count(y) )
var result = [Float](count: count(x), repeatedValue: 0)
x.withUnsafeBufferPointer { (xPointer: UnsafeBufferPointer<Float>) -> Void in
y.withUnsafeBufferPointer { (yPointer: UnsafeBufferPointer<Float>) -> Void in
vDSP_vmul(xPointer.baseAddress, 1, yPointer.baseAddress, 1, &result, 1, vDSP_Length(result.count))
}
}
The above isn't overly gross, and is easily extended to work with Double
types as well. But it still felt like too much repetition for me, so I decided to go one step further…
func operateOn<C: Unsafeable where C.Generator.Element == Float, C.Index == Int>( x: C, y: C, operation: (UnsafePointer<Float>, UnsafePointer<Float>, inout [Float], vDSP_Length) -> Void ) -> [Float] {
assert( count(x) == count(y) )
var result = [Float](count: count(x), repeatedValue: 0)
x.withUnsafeBufferPointer { (xPointer: UnsafeBufferPointer<Float>) -> Void in
y.withUnsafeBufferPointer { (yPointer: UnsafeBufferPointer<Float>) -> Void in
operation(xPointer.baseAddress, yPointer.baseAddress, &result, vDSP_Length(count(result)))
}
}
return result
}
This ball of warm hugs above allows me to shrink all those repetitive vDSP operations down considerably, like so:
public func mul<C: Unsafeable where C.Generator.Element == Float, C.Index == Int>( var x: C, y: C ) -> [Float] {
return operateOn(x, y) {
vDSP_vmul($0, 1, $1, 1, &$2, 1, $3)
return
}
}
Pretty slick, right?
[Float]
and Slice<Float>
?I'm glad you asked.
Consider this big-assed megapoint array:
let someGiantNastyArray = [Float](count: 1024 * 1024)
Now, what if you wanted to compute a waveform or something like that, where you only care about operations on segments of the larger data block.
Instead of this thing happening in a loop:
// Argh! A copy!
let someSegment = [Float](someGiantNastyArray[256..<512])
waveValue = fast_average(someSegment)
You could have this happening in a loop instead:
// No copies involved!
let someSegment: Slice<Float> = someGiantNastyArray[256..<512]
waveValue = fast_average(someSegment)
The first variant is making a brand-new array and copying the data from the larger array during each iteration, which is a Bad Idea for many reasons. (A life-sized Instruments icon would knock down your door and burn your house down, for starters.)
So, again, you can head on over to SMUGMath-Swift to check out a few of these operations, including an fft
and ifft
routine using the same principles.
I hope to keep trying nifty things out in this repo as time goes on (and as time permits.) Stay tuned.