我试图在Swift中有效地复制Apple的vDSP_deq22功能(来自Accelerate框架)而不使用专有代码,因此我可以跨平台使用它。
我已经把这个功能归结为一个看起来像这样的相对简单的循环(我能够在数学上分解原始方程的某些项):
// `input` is a vector of ~1024 Floats between 0 and 1
func filterBuffer(input: [Float]) -> Float {
// optimization:
let (b0, a1, a2) = (coefficients[0], coefficients[3], coefficients[4])
for x in input {
let y = b0 * (x - scratchSpace[1]) - a1 * scratchSpace[2] - a2 * scratchSpace[3]
// ... do something with y ...
// These are the problematic values: previous values of y are used
// in calculating the current value of y, i.e. the calculation is
// recursive, which makes it difficult to vectorize.
scratchSpace[3] = scratchSpace[2] // y[n-2]
scratchSpace[2] = y // y[n-1]
scratchSpace[1] = scratchSpace[0] // x[n-2]
scratchSpace[0] = x // x[n-1]
}
}
这大约是天真(非分解)实现的两倍,但Apple的功能仍然再次快50%(即我的版本需要50%的CPU时间)。
鉴于Apple的功能处于Accelerate框架中,我假设它使用某种SIMD指令和/或其他矢量化技巧。
按照这种思路,我试过这个:
// input gets multiplied by `b0` aka `coefficients[0]` and put into inputBuffer
// we saved the last two elements of inputBuffer
vDSP_vsmul(input, 1, coefficients, inputBuffer.advancedBy(2), 1, vDSP_Length(count)
// outputBuffer[n] = inputBuffer[n] - inputBuffer[n - 2]
vDSP_vsub(inputBuffer.advancedBy(2), 1, inputBuffer, 1, outputBuffer.advancedBy(2), 1, vDSP_Length(count))
for n in 2 ..< initedBufferLength {
outputBuffer[n] -= a1 * outputBuffer[n - 1] + a2 * outputBuffer[n - 2]
}
// outputBuffer now contains the y values from the first loop, perform work on them
由于某种原因,这比第一个解决方案慢约75%,即使执行的工作(在存储器访问,乘法,减法等方面)应该相同。实际上,x[n - 1]
和x[n - 2]
的值在循环的每次迭代中都没有被改组,因此整体上应该执行的工作量会减少。
“仪器”中的任何内容都没有让我知道可能需要花费多长时间,向量中的违规行&#39;优化&#39; (但实际上速度较慢)版本是for
循环,占用了函数CPU时间的90%左右。鉴于这个循环比第一个循环更简单(整体术语更少,乘法减少一个),我不明白为什么它会变慢。
问题是,如何将循环矢量化更快?从理论上讲,我的版本应该比Apple的vDSP_deq22更快,因为我可以分解一些术语,而他们必须总是假设输入系数方面的最坏情况。