1

I’m using buffer in many other vDSP operations before the code below. To those operations it is naturally a contiguous piece of memory, and that’s handy as there are vastly reduced number of calls to vDSP.


// […] many operations with buffer here


var buffer = UnsafeMutableBufferPointer<Double>.allocate(capacity: 10000000)

var sortedIndices: UnsafeMutablePointer<UInt> = UnsafeMutablePointer<UInt>.allocate(capacity: 10000000)

for (indexIndices, indexC) in buffer.indices.enumerated() {
    sortedIndices[indexIndices] = UInt(indexC)
}

vDSP_vsortiD(
    UnsafePointer<Double>(buffer.baseAddress)!, 
    sortedIndices, 
    nil, 
    UInt(buffer.count), 
    vDSP.SortOrder.descending.rawValue
)

I need to sort buffer sliced up. Also, I’d like not to copy its contents before every sort into smaller buffers as this is performance critical (as many times as possible per second).

vDSP_vsortiD() and vDSP_vsorti() do not support Slice.

For example this code is not possible since .baseAddress doesn’t exist on a Slice and I guess UnsafePointer doesn’t understand Slice either.

var sliceOfBuffer: Slice<UnsafeMutableBufferPointer<Double>> = buffer[0...30000]

vDSP_vsortiD(
    UnsafePointer<Double>(sliceOfBuffer.baseAddress)!, // This fails
    sortedIndices[0...30000], // This also fails
    nil, 
    UInt(30000), 
    vDSP.SortOrder.descending.rawValue
)

// Is there another sort indices function which accepts those types as params?

Many operations under vDSP accept Slice, however the above sort functions don’t.

Swift v5.6

oxygen
  • 5,891
  • 6
  • 37
  • 69

2 Answers2

1

Given that it's a C API, it's less error prone to just think a little more in C terms. Forget slices, just use pointers.

In C it would look something like this:

    double *buffer = (double *)calloc(size(double), 10000000);
    vDSP_Length *sortedIndices = (vDSP_Length*) calloc(size(vDSP_Length), 10000000);

    vDSP_vsortiD(
        buffer,
        sortedIndices,
        NULL,
        (vDSP_Length)30000,
        vDSP_SortOrder_descending // Probably a different name in reality
    );

And for sorting in the middle of the buffer it would look like this:

    const vDSP_Length sortOffset = 4000;
    const vDSP_Length sortCount  = 10000;

    vDSP_vsortiD(
        buffer + sortOffset,
        sortedIndices + sortOffset,
        NULL,
        sortCount,
        vDSP_SortOrder_descending // Probably a different name in reality
    );

In Swift the spelling of how you do that is a little different, but you do exactly the same thing. You have an UnsafeMutableBufferPointer, so you're on the right track that you need to use its baseAddress, but you don't need to do anything else to it. UnsafeMutablePointer types can be passed to UnsafePointer parameters for C calls (but not the other way around). sortedIndices is already a pointer rather than a buffer pointer, so you can just use it directly.

    vDSP_vsortiD(
        buffer.baseAddress!,
        sortedIndices,
        nil,
        vDSP_Length(30000),
        vDSP.SortOrder.descending.rawValue
    )

If instead, you want to sort somewhere in the middle of your buffer:

    // index range of 4000..<14_000
    let startOfSlice = 4000
    let lengthOfSlice = 10_000

    vDSP_vsortiD(
        buffer.baseAddress!.advanced(by: startOfSlice),
        sortedIndices.advanced(by: startOfSlice),
        nil,
        vDSP_Length(lengthOfSlice),
        vDSP.SortOrder.descending.rawValue
    )
Chip Jarred
  • 2,600
  • 7
  • 12
  • This doesn’t work, the sortedIndices does not contain the expected values. It is possible I may have done something wrong when reading the indices (tried reading both sortedIndices[0] and at the startOfSlice offset just in case…) Sorting does run but I think the values in the buffer have become uncorrelated with the indices. – oxygen Sep 22 '22 at 12:49
  • Maybe I was misunderstanding the problem. Did initializing the sortedIndices (per Martin R's answer) fix the problem for you? – Chip Jarred Sep 22 '22 at 12:57
  • I’ve always run with the indices initialized (see my edited post), so no. Martin’s answer works though, same indices initialized in the same way – oxygen Sep 22 '22 at 13:01
  • As for the way you read the indices, you'd read at whatever offset you advanced the pointer by, or 0 if you didn't advance, so it sounds like that wasn't the problem. Also you don't really need to offset the pointers by the same amount. If you want to always have the sorted indices at the start of `sortedIndices` you could just always pass that regardless of the offset into `buffer` you use. But I think initializing indices is key, and makes sense, since `vDSP_vsortiD` will have to use them to do its sort. – Chip Jarred Sep 22 '22 at 13:02
  • 1
    I read your update. You are right that `UnsafePointer` doesn't do slices. Unlike with the buffer pointers, often forget that the basic pointer types even have a subscript operator. When I use pointers, I advance to where I want to look at use `ptr.pointee`, which my profiling shows is faster than indexing through buffers (protocol witness table thunking overhead shows up in the profiler). Anyway, if Martin's answer works, I'm glad you have a solution. – Chip Jarred Sep 22 '22 at 13:11
  • It appears Swift does something magical behind the scenes to allow subsequent sorts by reusing the already sorted indices array without having to reinitialize. Rebasing buffers doesn’t work and results in uncorrelated sorts. Slices and .withUnsafeBufferPointer seem to be the only way. – oxygen Nov 14 '22 at 08:28
  • Likely it's `vDSP_vsortiD` that does the "magic" rather than Swift. Presumably it's using the indices to index into buffer to obtain values to compare, so if they're already sorted (or relatively sorted), it probably allows the sort to complete faster. It's good to know that you don't need to reinitialize them. – Chip Jarred Nov 16 '22 at 11:01
1

You can use withUnsafeBufferPointer() to pass the address of the slice's element storage to the vDSP function:

sliceOfBuffer.withUnsafeBufferPointer { bp in
    vDSP_vsortiD(
        bp.baseAddress!,
        sortedIndices,
        nil,
        UInt(sliceOfBuffer.count),
        vDSP.SortOrder.descending.rawValue
    )
}

With respect to slicing the indices: It seems that one can pass sortedIndices + sliceOfBuffer.startIndex to the vDSP function. However, the documentation states that the indices must be initialized starting at zero, so I would not rely on that.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382