Переменные класса освобождаются после вызова функции - PullRequest
0 голосов
/ 06 апреля 2019

У меня есть функция в быстром, как показано ниже. Существует цикл со ссылкой на переменные, существующие в экземпляре этого класса. (fftfilterbankReal - это массив в классе). Однако после одного прохода я получаю сообщение об ошибке «индекс вне диапазона» в строке кода для i в 0 ..

В отладчике кажется, что на 2-й итерации этого цикла нет переменных под выпадающим списком 'self'.

Если я закомментирую строку 'vDSP_zvmul (& kernel !, 1, & fft1Input, 1, & result, 1, vDSP_Length (r.count), 1)', тогда цикл запускается, и я могу отладить в любое время и визуально увидеть собственные переменные в отладчике.

Чего мне не хватает, что кажется, что эти переменные исчезают? Я прочитал о распределении памяти и тому подобное, и мои переменные класса объявлены с использованием 'var' и ничего более, поскольку по умолчанию это должно быть сильным в swift.

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 ]

1 голос
/ 06 апреля 2019

Ваш код является своего рода демонстрацией плохого использования при работе с массивами и указателями.

Например:

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

или:

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

DSPSplitComplex содержит два указателя для действительной части и мнимой части по отдельности и не копирует содержимое.Вы не должны передавать Swift Arrays для таких параметров.

И наиболее важной частью в вашем коде является ...

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 с вашим текущим кодом, вы разбиваете все содержимое стека, что вызывает то, что выиспытали:

В отладчике кажется, что на 2-й итерации этого цикла нет переменных под выпадающим списком 'self'.


Вы скрываете многие части своего кода, поэтому очень трудно угадать, что вы действительно хотите сделать, но если я переписываю ваш код более безопасным образом, это будет примерно так:

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])
            }
        }
    }

    //...
}

Могут быть исправлены другие детали, но в любом случае, пожалуйста, попробуйте и посмотрите, что вы получите.

0 голосов
/ 11 апреля 2019

Предисловие: Мальчик, просеивание всего этого заняло у меня почти 2 часа, и это все еще даже не идеально, но мальчик это намного приятнее.Надеюсь, это поможет!

Ваш код сильно страдает, потому что API-интерфейсы Accelerate - это API-интерфейсы C, которые не адаптированы для использования возможностей Swift.У вас будет гораздо более читабельный код, если вы сделаете себе несколько приятных обёрток для Accelerate API, который позволит вам убрать все «уродливые вещи» в угол, который вам редко приходится видеть или редактировать.

Я сделал этосоздав новый тип, ComplexFloatArray, который похож на DSPSplitComplex, но фактически инкапсулирует и владеет своими буферами.Это предотвращает висячие буферы, к которым DSPSplitComplex подвержен.

После обработки типов ComplexFloatArray пришло время определить некоторые оболочки для используемых вами функций ускорения.В этом случае vDSP_zvmul и vDSP_fft_zop.Поскольку в C нет кортежей, для возврата нескольких значений из функции C обычно требуется использование out-параметров, которые широко используются в инфраструктуре Accelerate.Мы можем перепроектировать их как функции Swift с обычными типами возврата.Эти API очень естественно выражены как методы экземпляра, которые работают с ComplexFloatArray, поэтому мы разместим их там.

Кроме того, ваш код значительно усложнен из-за его зависимости от внешнего состояния.Свертка - это функция, нет причины, по которой она делает что-либо, кроме ввода данных (через параметры, а , а не через переменные экземпляра) и возвращает результат (через возвращаемое значение, а не черезпеременные экземпляра).

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 копии любого данного неизменного значения.У вас есть все эти разные имена, относящиеся к одной и той же вещи, которая просто усложняет вещи.Можно привести аргумент, что это может быть полезно, если новые имена имеют значение, но такие имена, как x, y, re, im почти бесполезны в своих коммуникативных способностях и должны почти-всегда следует избегать.
  3. Массивы являются типами значений с Copy-on-Write.Вы можете сделать их копии, просто присвоив им новую переменную, поэтому код, подобный следующему:

    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).Но опять же, эти копии не нужны (как и x и y).Удалите их и просто используйте realsamples и imagsamples напрямую.

  4. Когда вы часто передаете несколько фрагментов данных вместе, это очень убедительный признак того, что вы должны определить новыйсовокупный тип, чтобы обернуть их.Например, если вы передаете два Array<Float> для представления комплексных векторов, вы должны определить тип ComplexVector.Это может позволить вам применять инварианты (например, всегда есть столько реальных значений, сколько мнимых значений) и добавлять удобные операции (например, func append(real: Float, imaginary: Float), который работает с обоими одновременно, гарантируя, что вы никогда не забудете добавить один из массивов).

В заключение,

Здесь много чего происходит, поэтому я не могу предвосхитить каждый вопрос и объяснить его заранее.Я призываю вас потратить некоторое время, прочитать это, и не стесняйтесь задавать мне любые дополнительные вопросы.

Я подозреваю, что допустил ошибки во время моегорефакторинг (потому что у меня не было тестовых примеров для работы), но код достаточно модульный, чтобы его было очень просто изолировать и исправить, а также ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...