arraysswiftaccelerate-frameworkvdsp

Better way to use ArraySlice with Accelerate functions?


I have some very large arrays that I have to perform millions of computations on. In Objective-C, the arrays would be stored as NSData and I'd abstract them to C arrays to use the Accelerate functions on (sum, add, etc). However, (given the obvious issues with using pointers everywhere) I'd love to make more use of the bounds checking that Swift arrays have built in. Therefore, I could use a nested withUnsafeBufferPointer for working with two arrays.

func mult(_ x: ArraySlice<Double>, _ y: ArraySlice<Double>) -> [Double] {
    assert(x.count == y.count)

    var results = [Double](repeating:0, count: x.count)

    x.withUnsafeBufferPointer({xBuffer in
        y.withUnsafeBufferPointer({yBuffer in
            vDSP_vmulD([Double](xBuffer), 1, [Double](yBuffer), 1, &results, 1, vDSP_Length(xBuffer.count))
        })
    })

    return results
}

var testArray = [Double]([0,1,2,3,4,5,6,7,8,9,10])
var testArray2 = [Double]([2,2,2,2,2,2,2,2,2,2,2])

let results = mult(testArray[5...10], testArray2[5...10])
print("\(results)")

First, having the recast the pointer as the intended type seems strange, when the compiler already knows how to cast the [Double] itself (the pointer passed inside the block is of type UnsafeBufferPointer<Double>, whereas the vDSP function is expects UnsafePointer<Double> (again, there is no complaint if I passed it the array variable itself)). Second, having to nest the withUnsafeBufferPointer looks strange, although I understand the usage. Finally, if I use ArraySlice<Double> as the input parameter type, then I can't generalize the function to both a Double array and a slice of that array.

Is there a better way to do this?


Solution

    1. The recast is indeed a problem, it creates a whole new array. To avoid it you can use the baseAddress property of the UnsafeBuffer (and unwrap it in Swift 3)

    2. The nested withUnsafeBufferPointer are indeed correct and can't be avoided (to my knowledge). The buffer pointer is only valid within the closure.

    3. You can create a protocol for that

    All in all, here is your code with these changes:

    import Accelerate
    
    protocol ArrayType {
        associatedtype Element
        var count : Int { get }
        func withUnsafeBufferPointer<R>(_ body: @noescape (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R
    }
    
    extension Array : ArrayType {}
    extension ArraySlice : ArrayType {}
    extension ContiguousArray : ArrayType {}
    
    func mult<A : ArrayType where A.Element == Double>(x: A, y: A) -> [Double] {
        assert(x.count == y.count)
    
        var result = [Double](repeating: 0, count: x.count)
    
        x.withUnsafeBufferPointer { x in
            y.withUnsafeBufferPointer { y in
                vDSP_vmulD(x.baseAddress!, 1, y.baseAddress!, 1, &result, 1, vDSP_Length(x.count))
            }
        }
    
        return result
    }
    
    var testArray1 : [Double] = [0,1,2,3,4,5,6,7,8,9,10]
    var testArray2 : [Double] = [2,2,2,2,2,2,2,2,2,2,2]
    
    let results = mult(x: testArray1[5...10], y: testArray2[5...10])
    print("\(results)")
    

    The forced unwrap will be fine, since the three conforming types won't ever give you a null pointer.