I have an audio analysis tool which I’m trying to parallelise, but it uses an escalating amount of heap space when working on a set of audio files.
It turns out that when I use the convert(to:error:withInputFrom:) method of AVAudioConverter inside a concurrentPerform block, a large amount of the memory associated with each conversion is retained until the very end of the concurrent performance, rather than being released at the end of each iteration’s execution. As such, the memory usage climbs and climbs while a batch of files is processed, rather than staying effectively flat during parallel operations.
I’m assuming that this is some flaw in my code rather than a bug in either framework, and hope someone can help me find it. See below a simplified version of the code, which converts the same file an arbitrary number of times (rather than operating on a large batch of different files as is the case in my real application).
Any help appreciated!
let outputFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 8000, channels: 1, interleaved: false)!
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.concurrentPerform(iterations: 1000) { index in
let inputFile = try! AVAudioFile(forReading: URL(filePath: "/path/to/some/song.m4a"))
let inputFormat = inputFile.processingFormat
let inputFrameCount = AVAudioFrameCount(inputFile.length)
let inputBuffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: inputFrameCount)!
try! inputFile.read(into: inputBuffer)
let sampleRateCoefficient = inputFormat.sampleRate / outputFormat.sampleRate
let outputFrameCount = UInt32(Double(inputFrameCount) / sampleRateCoefficient)
let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: outputFrameCount)!
let converter = AVAudioConverter(from: inputBuffer.format, to: outputFormat)!
let converterInputBlock: AVAudioConverterInputBlock = { _, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return inputBuffer
}
// If we just do some arbitrary work here, even big mallocs, memory usage is flat as everything is released at the end of the concurrentPerform closure.
// But if we run this conversion, each iteration retains a big chunk of memory until they're all finished. :(
converter.convert(
to: outputBuffer,
error: nil,
withInputFrom: converterInputBlock
)
}
}
Post
Replies
Boosts
Views
Activity
I'm building a custom UIAccessibilityElement for a complex control, and would like to expose both the .adjustable UIAccessibilityTrait (to move between discrete members listed in the control) and accessibilityCustomActions (to enable multiple possible interactions).
My (limited) experience is that both adjustment and custom action selection employ vertical swipes, so they collide if an element exposes both: vertical swipes call accessibilityIncrement and accessibilityDecrement, which I'm using to move between the members, and the accessibility element is described as having "Actions available" but there seems to be no way to access them.
Is there a way, beyond reimplementing my increment and decrement methods to append the custom actions to the discrete element list somehow, to enable a user to interrogate both these extra dimensions on a single accessibility element?
I'm using an NSTableView (and DifferenceKit, but that's likely irrelevant). This is all programmatic, no Interface Builder at all.
I'd previously implemented only tableView(_:objectValueFor:row) in order to get values into my table. At that point I could apply full and partial reloads and everything worked fine.
Now I've added an implementation of tableView(_:viewFor:row:) in order to format some columns differently, and it's affected reloading. A full reloadData() still works, but a call to reloadData(forRowIndexes:columnIndexes) doesn't call either my datasource or delegate methods; the reload seems to simply disappear.
I also tried removing the datasource method and running only with tableView(_:viewFor:row:) but no dice. A partial reload still doesn't call the delegate method.
Has anyone come across this? Is there a nuance of NSTableView I'm missing?
My (truncated) code:
init() {
...
tableView.dataSource = self
tableView.delegate = self
myColumnDefinitions.forEach {
tableView.addTableColumn($0)
}
}
func tableView(
_ tableView: NSTableView,
objectValueFor tableColumn: NSTableColumn?,
row: Int
) -> Any? {
...
return someStringProvider(column, row)
}
func tableView(
_ tableView: NSTableView,
viewFor tableColumn: NSTableColumn?,
row: Int
) -> NSView? {
...
let identifier = someDerivedValue(column)
if let existingView = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView {
existingView.textField?.stringValue = someStringProvider(column, row)
return existingView
}
let textField = NSTextField()
...
textField.stringValue = someStringProvider(column, row)
let view = NSTableCellView()
view.identifier = identifier
view.addSubview(textField)
view.textField = textField
view.addConstraints([
... (pin textField to view)
])
textField.bind(
.value,
to: view,
withKeyPath: "objectValue",
options: nil
)
return view
}