Article

Controlling vDSP Operations with Stride

Operate selectively on the elements of a vector at regular intervals.

Overview

Many functions in vDSP provide support for specifying a stride (that is, the distance between the elements that functions read from or write to) for a particular vector. For example, if you want to access consecutive elements, use a stride of 1 (referred to as a unit stride). If you want to access every third element, for example to work with the red channel in interleaved RGB data, use a stride of 3. If you want to access every second element, for example to work with a single audio channel in interleaved stereo audio data, use a stride of 2.

Typically, you use a unit stride. However, when you're copying the contents of an interleaved complex vector using vDSP_ctoz(_:_:_:_:_:), use a stride of 2. Use other strides to, for example, operate along a column of a matrix, where the stride is the number of elements per row.

Use a Unit Stride for Best Performance

For most of the functions in vDSP, you'll obtain the best performance when the stride is 1. Any other stride value generally prevents the use of vectorized code, greatly reducing both performance and energy efficiency.

The major exception to this limitation is in functions that support the use of interleaved complex data, such as vDSP_ctoz(_:_:_:_:_:) and vDSP_ztoc(_:_:_:_:_:). In these, the stride should be 2.

Set the Stride for Each Vector Independently

The following shows how you use vDSP_vadd(_:_:_:_:_:_:_:) to add each element in array a to the corresponding element in array b, and write the result to array c. Note that the stride used for each array is 1:

let strideA = vDSP_Stride(1)
let strideB = vDSP_Stride(1)
let strideC = vDSP_Stride(1)

let a: [Float] = [10, 20, 30, 40, 50, 60, 70, 80]
let b: [Float] = [ 1,  2,  3,  4,  5,  6,  7,  8]

let n = vDSP_Length(a.count)

var c = [Float](repeating: .nan,
                count: a.count)

vDSP_vadd(a, strideA,
          b, strideB,
          &c, strideC,
          n)

In this example, the result is [11.0, 22.0, 33.0, 44.0, 55.0, 66.0, 77.0, 88.0]:

A diagram how stride affects the operation of the vDSP_vadd function. There are three rows. The top row represents the first input, vector A, which has a stride of one. The second row represents the second input, vector B, which has a stride of one. The bottom row represents the output, vector C, which has a stride of one. The diagram has connecting lines from the input vectors to the output vector indicating the relationships between the inputs and output.

Use a Non-Unit Stride on Inputs

A non-unit stride allows you to, for example, access a particular color channel in interleaved RGB data. If you change strideB to 3, the operation adds the first, fourth, and seventh items (representing the red channel if array b contains interleaved RGB values) from array b to the first, second, and third items in array a, respectively:

let strideA = vDSP_Stride(1)
let strideB = vDSP_Stride(3)
let strideC = vDSP_Stride(1)
...
let n = vDSP_Length(3)
A diagram how stride affects the operation of the vDSP_vadd function. There are three rows. The top row represents the first input, vector A, which has a stride of one. The second row represents the second input, vector B, which has a stride of three. The bottom row represents the output, vector C, which has a stride of one. The diagram has connecting lines from the input vectors to the output vector indicating the relationships between the inputs and output.

Note that vDSP operations will always read n elements. Therefore, your arrays require at least (n - 1) * stride + 1 elements.

Use a Non-Unit Stride on Output

If you change array c's stride to 3, the calculation writes the result to its first, fourth, and seventh items. Using the example of interleaved RGB data discussed in Use a Non-Unit Stride on Inputs, this approach would write the result of an operation to only the red channel. The following example defines the stride for the input array, a, as 2, so the operation uses the first, third, and fifth elements:

let strideA = vDSP_Stride(2)
let strideB = vDSP_Stride(1)
let strideC = vDSP_Stride(3)
...
let n = vDSP_Length(3)
A diagram how stride affects the operation of the vDSP_vadd function. There are three rows. The top row represents the first input, vector A, which has a stride of two. The second row represents the second input, vector B, which has a stride of one. The bottom row represents the output, vector C, which has a stride of three. The diagram has connecting lines from the input vectors to the output vector indicating the relationships between the inputs and output.

Use a Negative Stride

Use a negative stride to access a vector in reverse order, for example, define the stride as −1 when convolving with a filter using vDSP_conv(_:_:_:_:_:_:_:_:)).

To use a negative stride, create a pointer to the last element in the array by initializing an UnsafePointer that's advanced by the number of elements less one. The following shows the Swift code required to effectively reverse a:

let strideA = vDSP_Stride(-1)
let strideB = vDSP_Stride(1)
let strideC = vDSP_Stride(1)
...
vDSP_vadd(UnsafePointer(a).advanced(by: a.count - 1), strideA,
          b, strideB,
          &c, strideC,
          n)

The result of adding a and b with a stride of -1 for a is [81.0, 72.0, 63.0, 54.0, 45.0, 36.0, 27.0, 18.0]:

A diagram how stride affects the operation of the vDSP_vadd function. There are three rows. The top row represents the first input, vector A, which has a stride of minus one. The second row represents the second input, vector B, which has a stride of one. The bottom row represents the output, vector C, which has a stride of one. The diagram has connecting lines from the input vectors to the output vector indicating the relationships between the inputs and output.

Use a Stride of 2 for Interleaved Complex Data

With interleaved complex data, vDSP stores alternating real and imaginary components consecutively. Use a stride of 2 for interleaved complex data, counting the individual component elements rather than counting complex numbers.

For example, use the following code to copy the contents of a DSPSplitComplex structure to an array of DSPComplex values:

var real: [Float] = [10, 20, 30, 40, 50, 60, 70, 80]
var imag: [Float] = [ 1,  2,  3,  4,  5,  6,  7,  8]
var splitComplex = DSPSplitComplex(realp: &real,
                                   imagp: &imag)

var complex = [DSPComplex](repeating: DSPComplex(),
                           count: real.count)

let n = vDSP_Length(real.count)

vDSP_ztoc(&splitComplex, 1,
          &complex, 2,
          n)

On return, complex contains the pairs [10.0 1.0], [20.0 2.0], [30.0 3.0] ... [80.0 8.0].

Conversely, use the following code to copy the values of an array of DSPComplex values to a DSPSplitComplex structure:

vDSP_ctoz(&complex, 2,
          &splitComplex, 1,
          n)

See Also

Signal Processing Essentials

Use Linear Interpolation to Construct New Data Points

Fill the gaps in arrays of numerical data using linear interpolation.

Using vDSP for Vector-based Arithmetic

Increase the performance of common mathematical tasks with vDSP vector-vector and vector-scalar operations.

Resampling a Signal with Decimation

Reduce the sample rate of a signal, by specifying a decimation factor and applying a custom antialiasing filter.

vDSP

Perform basic arithmetic operations and common digital signal processing routines on large vectors.