Swift DispatchQueue concurrentPerform OpenGL параллельный рендеринг - PullRequest
0 голосов
/ 13 февраля 2019

У меня есть безголовый рендер EGL в c ++ для Linux, который я обернул привязками для использования в Swift.Это прекрасно работает - я могу выполнять рендеринг параллельно, создавая несколько контекстов и рендеринг в отдельных потоках, но я столкнулся со странной проблемой.Прежде всего, я обернул все вызовы GL, относящиеся к рендереру, и его контекст внутри собственной последовательной очереди, как показано ниже.

func draw(data:Any) -> results {
  serial.sync {
    //All rendering code for this renderer is wrapped in a unique serial queue.
    bindGLContext()
    draw()
  }
}

Для пакетной обработки данных между рендерерами я использовал DispatchQueue.concurrentPerform.Это работает правильно, но когда я пытаюсь создать параллельную очередь с DispatchGroup, происходит нечто странное.Несмотря на то, что я обернул все вызовы GL в последовательные очереди, контексты GL запутались, и все вызовы gl не в состоянии распределить текстуры / буферы / и т. Д.

Поэтому я пытаюсь понять разницу между этими двумя и почему однаработает, а другой нет.Любые идеи будут великолепны!

//This works
DispatchQueue.concurrentPerform(iterations: renderers.count) { j in
  let batch = batches[j]
  let renderer = renderers[j]
  let _ = renderer.draw(data:batch)
}
//This fails – specifically GL calls fail
let group = DispatchGroup()
let q = DispatchQueue(label: "queue.concurrent", attributes: .concurrent)
for (j, renderer) in renderers.enumerated() {
   q.async(group: group) {
     let batch = batches[j]
     let _ = renderer.draw(data:batch)
   }
}
group.wait()

1 Ответ

0 голосов
/ 19 февраля 2019

Редактировать:

Я бы хотел убедиться, что оболочка OpenGL действительно поточно-ориентированная.Каждый рендер, имеющий свою собственную последовательную очередь, может не помочь, если несколько рендеров делают вызовы OpenGL одновременно.Возможно, версия DispatchQueue.concurrentPerform работает, потому что она работает только последовательно.

Оригинальный ответ:

Я подозреваю, что сбои OpenGL связаны с ограничениями памяти.Когда вы отправляете много задач в параллельную очередь, GCD не делает ничего умного, чтобы ограничить число запускаемых задач.Если куча запущенных задач блокируется с помощью ввода-вывода, он может просто запустить все больше и больше задач, прежде чем любая из них завершится, сожрать все больше и больше памяти. Вот подробное описание Майка Эша о проблеме .

Я бы предположил, что DispatchQueue.concurrentPerform работает, потому что у него есть какая-то дополнительная логика внутри, чтобы избежать порождения слишком большого количества потоков, хотя это плохо документировано, и здесь могут происходить вещи, специфичные для платформы.Я не уверен, почему эта функция вообще существует, если все, что она делает, отправляет в параллельную очередь.

Если вы хотите отправить большое количество элементов непосредственно в DispatchQueue, особенно если эти элементыЕсли у вас есть не связанные с процессором компоненты, вам нужно добавить дополнительную логику, чтобы ограничить число запускаемых задач.Вот пример из Справочника Сороуса Ханлоу по GCD :

class LimitedWorker {
    private let serialQueue = DispatchQueue(label: "com.khanlou.serial.queue")
    private let concurrentQueue = DispatchQueue(label: "com.khanlou.concurrent.queue", attributes: .concurrent)
    private let semaphore: DispatchSemaphore

    init(limit: Int) {
        semaphore = DispatchSemaphore(value: limit)
    }

    func enqueue(task: @escaping () -> ()) {
        serialQueue.async(execute: {
            self.semaphore.wait()
            self.concurrentQueue.async(execute: {
                task()
                self.semaphore.signal()
            })
        })
    }
}

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

Вы можете использовать его следующим образом:

let group = DispatchGroup()
let q = LimitedWorker(limit: 10) // Experiment with this number
for (j, renderer) in renderers.enumerated() {
   group.enter()
   q.enqueue {
     let batch = batches[j]
     let _ = renderer.draw(data:batch)

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