传递对数组元素的引用以加速

Passing a reference to an element of an array to Accelerate

Swift 语言的一个烦恼是它不断变化的语法(以及语法转换器的不可靠性)。 那,还有语言设计者对指针的恐惧。

我一直在处理大量音频数据。当然,我也使用 Apple 相当不错的 Accelerate 框架来进行基本操作,例如 FFT、窗口函数等。

如果我有这样的数组:

var veryBigArray = Float[重复:0.0,计数:1024 * 1024]

我想对 1024 个元素进行窗口操作 + FFT,从元素 n 开始,结果证明这非常棘手。对第 n 个元素的引用导致编译器将第 n 个元素复制到其他地方,然后给我一个对新临时位置的引用。当然,这对我来说完全没用。这大概是因为引用可能导致元素内容发生变化,从而触发写时复制(我认为这使对可变性的恐惧超出了合理范围 - 类 已经有了这种的可变性,天还没塌下来)。

我可以使用:

数组(veryBigArray[n ..< n+1024])

但这会导致可怕的复制操作,如果在循环中完成,很快就会让机器崩溃。我可以传递一个切片,但我不知道可能发生的任何未记录的复制操作的规则是什么。

我实际上已经开始将其迁移到 C,当我认为也许我可以使用这样的东西时:

    var basePtr = UnsafeMutableBufferPointer(start: &veryBigArray, count: veryBigArray.count)
    let ptr = UnsafePointer<Float>(basePtr.baseAddress)
    var ptr1 = ptr! + n

现在我可以调用 Accelerate 函数了。例如:

    vDSP_sve(ptr1, 1, &aResultFloat, vDSP_Length(1024))

这确实有效,甚至可能是我最好的选择(考虑到我在 Swift 上投入了大量资金,尽管使用 C 本来是一个更合理的选择)。缺点主要是它很笨重,而且指针是无主的,这排除了使用 ARC 的可能性。

展开一点,我所做的是从资源中读取音频缓冲区。我想要一个指向缓冲区的自有指针,当我希望 ARC 处理缓冲区时,我可以将其清空。

这是我阅读资源的方式:

func loadAudioFile(fileName: String) -> (UnsafePointer<Float>?, Int) {
//  let audioSession = AVAudioSession.sharedInstance()

let fileURL = Bundle.main.url(forResource: fileName, withExtension: "aiff")
if fileURL == nil {return (nil, 0) }
if let audioFile = try? AVAudioFile(forReading: fileURL!) {
    let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: UInt32(audioFile.length))
    if ((try? audioFile.read(into: buffer)) != nil) {
        return (UnsafePointer(buffer.floatChannelData?.pointee), Int(audioFile.length))
    }
}
return (nil, 0)

}

但我想知道是否有更好的选择?

ContiguousArray 原则上很有吸引力,但它基本上没有文档记录,而且与常规数组的互操作性不是特别好。它还遭受了一个看似非常愚蠢的决定,即不允许对数组元素进行地址引用(它们的处理方式与常规数组相同的愚蠢方式)。

我可以在我的系统中来回传递 UnsafeMutableBufferPointers。如果我给这个绕口令一个类型别名(此后已重命名为 associatedtype,让任何人真正猜测它的用途),它可能并不完全可怕......而且它具有保证连续的好处。但它似乎比桥接到 obj-c 来完成繁重的工作更笨重。

我不得不承认我对在我看来非常简单、非常常见的操作感到沮丧,这在 Swift 中几乎不可能实现。

Chris Liscio 几年前发布了迄今为止我见过的最好的东西。当然,自从他发布后,语法发生了变化,语法转换器不适用于他的代码,但我当然可以以此为起点,构建满足我需要的东西。

当 Swift 宣布时,我真的很兴奋。该语言似乎设计得很好,甚至可以编译成本地代码,这对我来说很重要。它应该与 C 互操作。所以我对它做出了重大承诺。

但过去两年的特点是对不可靠的编译器、似乎随每个 beta 版本而变化的语法以及核心 Swift 团队对真正想要使用他们的玩具语言来做真正的工作(这就是为什么我认为提出一个保证无处可去的语言提案没有意义)。

有没有人有更好的方法来解决将大音频缓冲区内的位置地址传递给 Accelerate 函数的问题?随着时间的流逝,我发现有更多的聪明人找到了比我更好的做事方法,所以我很想听听任何有好的解决方案的人的意见。或者甚至是一个可能指向一个好的解决方案的想法。

UnsafeMutableBufferPointer 的方向是正确的,但是 是(至少理论上)一个问题:根据文档

var basePtr = UnsafeMutableBufferPointer(start: &veryBigArray, count: veryBigArray.count)

传递一个指向数组开头的指针,它是“生命周期延长的 在通话期间”。这意味着指针不是 保证在UnsafeMutableBufferPointer后有效 构造函数 returns。

也不需要可变指针因为vDSP_sve() 不修改给定的数组。

(在我看来)正确的解决方案是

veryBigArray.withUnsafeBufferPointer {
    vDSP_sve([=11=].baseAddress! + n, 1, &aResultFloat, vDSP_Length(1024))
}

它传递一个指向数组连续的指针 存储到关闭。如果不存在这样的存储,则首先创建它。

需要指点的话还有withUnsafeMutableBufferPointer() 到数组的可变连续存储。