传递对数组元素的引用以加速
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()
到数组的可变连续存储。
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()
到数组的可变连续存储。