Передача ссылки на элемент массива в Accelerate

Одним из недостатков языка Swift является его бесконечно изменяющийся синтаксис (и ненадежность преобразователя синтаксиса). Это и страх разработчика языка перед указателями.

Я работал с большими массивами аудиоданных. Естественно, я также использую довольно приятную структуру Apple Accelerate для таких операций, как БПФ, оконные функции и тому подобное.

Если у меня есть массив примерно так:

var veryBigArray = Float [повторение: 0,0, количество: 1024 * 1024]

и я хочу выполнить оконную операцию + БПФ на 1024 элементах, начиная с элемента 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

Теперь я могу вызывать функции ускорения. Например:

    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 взад и вперед по моей системе. Если я дам этой скороговорке типалиас (который с тех пор был переименован в связанный тип, пусть кто-нибудь на самом деле догадывается, для чего он нужен), это может быть не совсем ужасно ... и у него есть преимущество в том, что он гарантирует непрерывность. Но это кажется даже более громоздким, чем переход к obj-c для выполнения тяжелой работы.

Я должен признаться, что разочарован тем, что мне кажется очень простой и очень распространенной операцией, которую практически невозможно реализовать в Swift.

Лучшее, что я видел до сих пор, было опубликовано Крисом Лиссио пару лет назад. Конечно, с тех пор, как он опубликовал его, синтаксис изменился, и преобразователь синтаксиса не работает с его кодом, но я, безусловно, мог бы использовать его как отправную точку и создать что-то, что удовлетворяет мои потребности.

Я был очень взволнован, когда объявили о Swift. Язык казался хорошо спроектированным и даже скомпилированным в нативный код, что было для меня важно. Предполагалось, что он будет совместим с C. Так что я взял на себя серьезное обязательство.

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

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


person Andromeda    schedule 14.10.2016    source источник


Ответы (1)


Вы на правильном пути с UnsafeMutableBufferPointer, но есть (по крайней мере теоретически) проблема: Согласно документации

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

передает указатель на начало массива, который «продлевается на время вызова на время вызова». Это означает, что не гарантируется, что указатель будет действительным после возврата конструктора UnsafeMutableBufferPointer.

Также нет необходимости в изменяемом указателе, потому что vDSP_sve() не изменяет данный массив.

На мой взгляд, правильное решение -

veryBigArray.withUnsafeBufferPointer {
    vDSP_sve($0.baseAddress! + n, 1, &aResultFloat, vDSP_Length(1024))
}

который передает указатель на непрерывное хранилище массива в замыкание. Если такого хранилища нет, оно сначала создается.

Также существует withUnsafeMutableBufferPointer(), если вам нужен указатель на изменяемое непрерывное хранилище массива.

person Martin R    schedule 14.10.2016
comment
Спасибо за ваш комментарий! Вы правильно указали на связанную проблему, которая меня беспокоит, а именно на то, как создать указатель на аудиобуфер, которым можно управлять через ARC. Итак, если я получаю буфер через чтение ресурса, я хочу, чтобы указатель владел буфером - что, как вы говорите, не относится к UnsafeBufferPointer или его изменяемому кузену. Насколько я могу судить, ничто из этого семейства указателей не владеет хранилищем. - person Andromeda; 14.10.2016
comment
@Andromeda: На самом деле это была попытка ответить на конкретную проблему в вашем вопросе :) - Я не уверен, что вы все еще говорите о той же проблеме. Но если вы хотите прояснить вопрос, я предлагаю соответствующим образом обновить его. В настоящее время он не упоминает о чтениях ресурсов. - person Martin R; 14.10.2016
comment
Верно - я просто добавил комментарий на случай, если у вас или у кого-то есть идея. Как вы говорите, это не связано с исходным вопросом / тирадой. - person Andromeda; 14.10.2016