调用函数后释放类变量

时间:2019-04-06 16:51:41

标签: swift loops variables memory

我有一个迅速的功能,如下所示。存在一个引用此类实例中现有变量的循环。 (fftfilterbankReal是类中的数组)。但是,经过一遍之后,我在代码行“ for in in 0.”中遇到“索引超出范围”的错误。

在调试器中,似乎在该循环的第二次迭代中,“ self”下拉列表下没有变量。

如果我注释掉'vDSP_zvmul(&kernel !, 1,&fft1Input,1,&result,1,vDSP_Length(r.count),1)'行,则循环运行,我可以随时调试并直观地看到调试器中的自变量。

我想让这些变量消失的地方是什么?我已经读过内存分配之类的东西,并且我的类变量是使用'var'声明的,仅此而已,因为它默认情况下应迅速设为Strong。

func convolveInput(realsamples:[Float], imagsamples:[Float]) -> [Float]{
    realResult = Array(repeating: [], count: filterbankReal.count)
    imagResult = Array(repeating: [], count: filterbankReal.count)
    let x = realsamples
    let y = imagsamples
    var N = x.count
    var logN = 16          
    var fft1Setup = vDSP_create_fftsetup(UInt(logN), FFTRadix(FFT_RADIX2))!

    var paddedLength = x.count + filterbankReal.count - 1
    var halfPaddedLength = paddedLength/2
    var halfKernelLength = kernelLength/2
    //setup Complex Buffer 1
    var reals = [Float]()
    var imags = [Float]()
    for i in 0..<x.count{
        reals.append(x[i])
        imags.append(y[i])

    }
    var complexBuffer1 = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))

    //Perform FFT on incoming samples
    var re = [Float](repeating:0.0, count: N)
    var im = [Float](repeating:0.0, count: N)
    var fft1Input = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: re), imagp: UnsafeMutablePointer(mutating: im))

    var fftlength = 10
    vDSP_fft_zop(fft1Setup, &(complexBuffer1), 1, &fft1Input, 1, UInt(fftlength), Int32(FFT_FORWARD))

    //Remove DC from FFT Signal
    re.remove(at: 0)
    im.remove(at: 0)

for i in 0..<self.fftfilterbankReal.count {
 var r:[Float] = self.fftfilterbankReal[i]           
        var im:[Float] = self.fftfilterbankImag[i]          
        var kernel:DSPSplitComplex? = DSPSplitComplex(realp: &r, imagp: &im)
var res:Float = 0
        var ims:Float = 0
        var result:DSPSplitComplex = DSPSplitComplex(realp: &res, imagp: &ims)
        vDSP_zvmul(&kernel!, 1, &fft1Input, 1, &result, 1, vDSP_Length(r.count), 1)
        self.realResult[i].append(res)
        self.imagResult[i].append(ims)
    }

2 个答案:

答案 0 :(得分:1)

您的代码很好地展示了使用数组和指针时的不良用法。

例如:

var complexBuffer1 = DSPSplitComplex(realp: UnsafeMutablePointer(mutating: reals), imagp: UnsafeMutablePointer(mutating: imags))

或:

var kernel:DSPSplitComplex? = DSPSplitComplex(realp: &r, imagp: &im)

DSPSplitComplex分别为实部和虚部保留两个指针,并且不复制内容。您不应为此类参数传递Swift数组。

代码中最关键的部分是...

var res:Float = 0
var ims:Float = 0
var result:DSPSplitComplex = DSPSplitComplex(realp: &res, imagp: &ims)
vDSP_zvmul(&kernel!, 1, &fft1Input, 1, &result, 1, vDSP_Length(r.count), 1)

vDSP_zvmul生成N(在您的代码N = vDSP_Length(r.count)中)复数,因此您需要准备一个可以容纳N元素的区域。

使用当前代码调用vDSP_zvmul后,您会破坏整个堆栈内容,这会导致您遇到以下问题:

  

在调试器中,似乎在该循环的第二次迭代中,   在“自我”下拉菜单下没有任何变量。


您隐藏了代码的许多部分,因此很难猜测您真正想要做什么,但是如果我以更安全的方式重新编写代码,则可能是这样的:

func convolveInput(realsamples:[Float], imagsamples:[Float]) -> [Float]{
    realResult = Array(repeating: [], count: filterbankReal.count)
    imagResult = Array(repeating: [], count: filterbankReal.count)
    let x = realsamples
    let y = imagsamples
    var N = x.count
    var logN = 16
    var fft1Setup = vDSP_create_fftsetup(UInt(logN), FFTRadix(FFT_RADIX2))!

    var paddedLength = x.count + filterbankReal.count - 1
    var halfPaddedLength = paddedLength/2
    var halfKernelLength = kernelLength/2
    //setup Complex Buffer 1
    var reals = UnsafeMutableBufferPointer<Float>.allocate(capacity: x.count)
    defer {reals.deallocate()}
    var imags = UnsafeMutableBufferPointer<Float>.allocate(capacity: y.count)
    defer {imags.deallocate()}
    _ = reals.initialize(from: x)
    _ = imags.initialize(from: y)
    var complexBuffer1 = DSPSplitComplex(realp: reals.baseAddress!, imagp: imags.baseAddress!)

    //Perform FFT on incoming samples
    var re = UnsafeMutableBufferPointer<Float>.allocate(capacity: N)
    defer {re.deallocate()}
    var im = UnsafeMutableBufferPointer<Float>.allocate(capacity: N)
    defer {im.deallocate()}
    var fft1Input = DSPSplitComplex(realp: re.baseAddress!, imagp: im.baseAddress!)

    let fftlength = 10
    vDSP_fft_zop(fft1Setup, &complexBuffer1, 1, &fft1Input, 1, UInt(fftlength), Int32(FFT_FORWARD))

    //Remove DC from FFT Signal
    fft1Input = DSPSplitComplex(realp: re.baseAddress!+1, imagp: im.baseAddress!+1)

    for i in 0..<self.fftfilterbankReal.count {
        self.fftfilterbankReal[i].withUnsafeMutableBufferPointer {rBuf in
            self.fftfilterbankImag[i].withUnsafeMutableBufferPointer {imBuf in
                var kernel = DSPSplitComplex(realp: rBuf.baseAddress!, imagp: imBuf.baseAddress!)
                var res = UnsafeMutableBufferPointer<Float>.allocate(capacity: rBuf.count)
                defer {res.deallocate()}
                var ims = UnsafeMutableBufferPointer<Float>.allocate(capacity: rBuf.count)
                defer {ims.deallocate()}
                var result:DSPSplitComplex = DSPSplitComplex(realp: res.baseAddress!, imagp: ims.baseAddress!)
                vDSP_zvmul(&kernel, 1, &fft1Input, 1, &result, 1, vDSP_Length(rBuf.count), 1)
                //vDSP_zvmul generates `N` complex numbers,
                // I do not understand what you really want to do...
                self.realResult[i].append(res[0])
                self.imagResult[i].append(ims[0])
            }
        }
    }

    //...
}

可能还有其他部分需要修复,但是无论如何,请尝试看看会得到什么。

答案 1 :(得分:0)

前言:男孩,筛选所有这些花了我将近2个小时,虽然还算不上完美,但是男孩比这更好了。希望对您有所帮助!

由于Accelerate API是不适合利用Swift功能的C API,因此您的代码遭受了巨大损失。如果您为Accelerate API做一些漂亮的包装,您将获得更具可读性的代码,该包装使您可以将所有“丑陋的东西”塞进很少见或无需编辑的角落。

我通过创建一个新类型ComplexFloatArray来实现这一点,该类型类似于DSPSplitComplex,但实际上封装并拥有其缓冲区。这样可以防止DSPSplitComplex容易受到影响的悬空缓冲区。

使用ComplexFloatArray类型后,是时候为您使用的Accelerate函数定义一些包装器了。在这种情况下,vDSP_zvmulvDSP_fft_zop。由于C没有元组,因此从C函数返回多个值通常需要使用out-parameters,这些参数在Accelerate框架中普遍使用。我们可以将它们重新设计为具有常规返回类型的Swift函数。这些API非常自然地表示为可在ComplexFloatArray上运行的实例方法,因此我们将它们放在此处。

此外,您的代码由于依赖于外部状态而变得更加复杂。卷积是一个函数,除了输入(通过参数,而 not 通过实例变量)并返回结果(通过返回值,而 not ),它除了执行输入操作外没有其他原因>通过实例变量)。

import Accelerate

class ComplexFloatArray {
    var reals: [Float]
    var imaginaries: [Float]

    init(reals: [Float], imaginaries: [Float]) {
        self.reals = reals
        self.imaginaries = imaginaries
    }
}

extension ComplexFloatArray { // Core features
    var count: Int {
        assert(reals.count == imaginaries.count)
        return reals.count
    }

    static let stride = 1

    func append(real: Float, imaginary: Float) {
        self.reals.append(real)
        self.imaginaries.append(imaginary)
    }

    func useAsDSPSplitComplex<R>(_ closure: (inout DSPSplitComplex) -> R) -> R {
        return reals.withUnsafeMutableBufferPointer { realBufferPointer in
            return imaginaries.withUnsafeMutableBufferPointer { imaginaryBufferPointer in
                var dspSplitComplex = DSPSplitComplex(realp: realBufferPointer.baseAddress!, imagp: imaginaryBufferPointer.baseAddress!)
                return closure(&dspSplitComplex)
            }
        }
    }
}

extension ComplexFloatArray { // Convenience utilities
    convenience init() {
        self.init(reals: [], imaginaries: [])
    }

    static func zeros(count: Int) -> ComplexFloatArray {
        return ComplexFloatArray(reals: Array(repeating: 0, count: count), imaginaries:Array(repeating: 0, count: count))
    }
}

extension ComplexFloatArray { // Vector multiplciation extensions

    enum ComplexMultiplicationType: Int32 { case normal = 1, conjugate = -1 }

    func complexMultiply(
        with other: ComplexFloatArray,
        multiplicationType: ComplexMultiplicationType = .normal
    ) -> ComplexFloatArray {

        assert(self.count == other.count, "Multiplied vectors must have the same size!")

        let result = ComplexFloatArray.zeros(count: self.count)

        self.useAsDSPSplitComplex { selfPointer in
            other.useAsDSPSplitComplex { otherPointer in
                result.useAsDSPSplitComplex { resultPointer in
                    vDSP_zvmul(
                        &selfPointer, ComplexFloatArray.stride,
                        &otherPointer, ComplexFloatArray.stride,
                        &resultPointer, ComplexFloatArray.stride, vDSP_Length(result.count),
                        multiplicationType.rawValue)
                }
            }
        }

        return result
    }
}

extension ComplexFloatArray { // FFT extensions
    enum FourierTransformDirection: Int32 { case forward = 1, inverse = -1 }

    //TODO: name log2n label better
    func outOfPlaceComplexFourierTransform(
        setup: FFTSetup,
        resultSize: Int,
        log2n: UInt, 
        direction: FourierTransformDirection
    ) -> ComplexFloatArray {
        let result = ComplexFloatArray.zeros(count: resultSize)

        self.useAsDSPSplitComplex { selfPointer in
            result.useAsDSPSplitComplex{ resultPointer in
                vDSP_fft_zop(
                    setup,
                    &selfPointer, ComplexFloatArray.stride,
                    &resultPointer, ComplexFloatArray.stride,
                    log2n,
                    direction.rawValue
                )
            }
        }

        return result
    }
}

extension FFTSetup {
    enum FourierTransformRadix: Int32 {
        case radix2 = 0, radix3 = 1, radix5 = 2

        // Static let constants are only initialized once
        // This function's intent to to make sure this enum stays in sync with the raw constants the Accelerate framework uses
        static let assertRawValuesAreCorrect: Void = {
            func assertRawValue(for actual: FourierTransformRadix, isEqualTo expected: Int) {
                assert(actual.rawValue == expected, "\(actual) has a rawValue of \(actual.rawValue), but expected \(expected).")
            }
            assertRawValue(for: .radix2, isEqualTo: kFFTRadix2)
            assertRawValue(for: .radix3, isEqualTo: kFFTRadix3)
            assertRawValue(for: .radix5, isEqualTo: kFFTRadix5)
        }()
    }

    init(log2n: Int, _ radix: FourierTransformRadix) {
        _ = FourierTransformRadix.assertRawValuesAreCorrect

        guard let setup = vDSP_create_fftsetup(vDSP_Length(log2n), FFTRadix(radix.rawValue)) else {
            fatalError("vDSP_create_fftsetup(\(log2n), \(radix)) returned nil")
        }
        self = setup
    }
}


struct NameMe {
    // I don't know what this is, but if it can somehow be removed,
    // the whole convolveInput method could be moved into an extension on ComplexFloatArray.
    var fftFilterBank: [ComplexFloatArray]

    func convolve(samples: ComplexFloatArray) -> [ComplexFloatArray] {
//          TODO: rework reimplement this code to remove the DC from samples, and add it back in
//          //Remove DC from FFT Signal
//          re.remove(at: 0)
//          im.remove(at: 0)

        let fftlength: UInt = 10 // Todo: what is this, exactly?

        let fft1Input = samples.outOfPlaceComplexFourierTransform( // Rename me to something better
            setup: FFTSetup(log2n: 16, .radix2),
            resultSize: samples.count,
            log2n: fftlength,
            direction: .forward
        )

        return self.fftFilterBank.map { kernel in kernel.complexMultiply(with: fft1Input) }
    }

    // Stub for compatibility with the old API. Deprecate it and move to the
    // `convolve(samples: ComplexFloatArray) -> [ComplexFloatArray]` as soon as possible.
    func convolveInput(realsamples: [Float], imagsamples: [Float]) -> [ComplexFloatArray] {
        return self.convolve(samples: ComplexFloatArray(reals: realsamples, imaginaries: imagsamples))
    }
}

一路上我有一些笔记

  1. 此功能 WAAAAAAAAAAAY 的时间过长。如果您的函数长度超过10行,则有很强的迹象表明它的大小太大了,可以做很多事情,并且可以通过分解成更简单的步骤来受益。
  2. 您有很多冗余变量。任何给定的不可变值都不需要多于1个副本。您拥有所有这些不同的名称,指的是同一件事,这只会使事情复杂化。可能会有一个论点,那就是如果新名称有意义,则可以使用此名称,但是xyreim之类的名称几乎是无用的沟通能力,几乎应该完全避免。
  3. 数组是具有“写时复制”的值类型。您只需将它们分配给一个新变量即可复制它们,因此代码如下:

    var reals = [Float]()
    var imags = [Float]()
    for i in 0..<x.count{
        reals.append(x[i])
        imags.append(y[i])
    
    }
    

    既慢,又在视觉上很麻烦。这可能很简单:let (reals, imags) = (x, y)。但是同样,这些副本不是必需的(xy也是如此)。删除它们,然后直接使用realsamplesimagsamples

  4. 当您发现自己经常将多个数据传递在一起时,这非常有力地表明您应该定义一个新的聚合类型来包装它们。例如,如果要传递两个Array<Float>来表示复数向量,则应定义一个ComplexVector类型。这可以让您强制执行不变式(例如,实数值总是与虚值一样多),并添加方便的操作(例如func append(real: Float, imaginary: Float),该操作可以同时对这两个数值进行操作,从而确保您永远不会忘记附加到其中一个)数组)。

最后,

这里发生了很多事情,因此我无法提前解决每个问题并提前进行解释。我鼓励您花一些时间,通读此书,然后 随时问我任何后续问题。

我怀疑我在重构过程中犯了错误(因为我没有可使用的测试用例),但是该代码具有足够的模块化特性,因此应该很容易隔离,修复和错误。