Многопоточный рендеринг и пулы команд

Никол Болас:

В конце концов, возможность параллельно создавать командные буферы - одно из преимуществ Vulkan.

Технические характеристики (пулы команд 5.1) (выделено мной):

Пулы команд синхронизируются с приложением, что означает, что пул команд не должен использоваться одновременно в нескольких потоках. Это включает использование посредством записи команд для любых командных буферов, выделенных из пула, а также операции, которые выделяют, освобождают и сбрасывают командные буферы или сам пул.

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

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

Я лично не знаю, почему бы вам просто не записать все заранее. Так почему же так необходимо параллельное создание командных буферов? И действительно ли вам тогда нужно использовать один пул на поток?


person Shahbaz    schedule 12.07.2016    source источник
comment
Мне нравится, как вы просто поставили Николаса Боласа в первую строчку - он, кажется, может ответить на все вопросы о Вулкане, которые я могу придумать. Необходимость использовать отдельный пул для каждого потока не уничтожает назначение буферов команд - в какой ситуации пул, из которого выделяется буфер команд, ограничивает набор команд, которые вы можете поместить в буфер?   -  person Andrew Williamson    schedule 12.07.2016
comment
@AndrewWilliamson, очень плохая опечатка, я имел в виду цель команд пулы.   -  person Shahbaz    schedule 12.07.2016
comment
Каждый поток может по-прежнему очень часто создавать и уничтожать буферы команд. Пулы существуют для этого сценария.   -  person Andrew Williamson    schedule 12.07.2016
comment
@AndrewWilliamson, зачем им это делать? В каком сценарии это было бы лучше, чем повторное использование буферов команд?   -  person Shahbaz    schedule 12.07.2016
comment
@Shahbaz Может быть проще не заботиться о сбросе и повторном использовании буферов команд. Выделение новых буферов команд обходится недорого, если пулу не нужно обращаться к хосту для получения дополнительной памяти. Возможно, вам поможет эта статья: community.arm.com/groups/arm-mali-graphics/blog/2016/04/19/ В этой статье для каждого потока используется несколько пулов, каждый из которых назначается обработанному кадру. После завершения рендеринга кадра соответствующий пул сбрасывается и готов к повторному использованию.   -  person Manuzor    schedule 30.10.2016


Ответы (2)


Если вы собираетесь вести параллельную запись, вам лучше иметь отдельный пул для каждого потока, не так ли?

Я не понимаю, как наличие отдельного пула для каждого потока «убивает всю цель пулов команд, когда дело доходит до параллельной записи». На самом деле, это немного помогает, поскольку каждый поток может управлять своим собственным пулом команд по своему усмотрению.

Рассмотрим структурную разницу между, скажем, пулом дескрипторов и пулом команд. С пулом дескрипторов вы в основном говорите ему, что именно вы из него выделите. VkDescriptorPoolCreateInfo предоставляет подробную информацию, которая позволяет реализациям распределять заранее точно, сколько памяти вы будете использовать для каждого пула. И вы не можете выделить больше этого из пула дескрипторов.

Напротив, VkCommandPoolCreateInfo не содержит ... ничего. О, вы скажете, могут ли буферы команд быть первичными или вторичными. Вы говорите, будут ли буферы команд часто сбрасываться или постоянными. И еще пара вещей. Но кроме этого вы ничего не говорите о содержимом буферов команд. Вы даже не даете ему информацию о том, сколько буферов вы выделите.

Пулы дескрипторов должны быть фиксированными: выделяться по мере необходимости, но в пределах количества, установленного во время создания. Командные буферы должны быть очень динамичными: выделяться по мере необходимости для ваших конкретных случаев использования.

Думайте об этом как о каждом пуле, имеющем свой malloc / free. Поскольку пользователь вынужден синхронизировать доступ к пулам и их буферам, это означает, что каждая vkCmd* функция не обязана делать это при выделении памяти. Это ускоряет создание команды. Это помогает многопоточности. Когда поток решает сбросить свой пул команд, ему не нужно блокировать какие-либо мьютексы или другие подобные вещи, чтобы сделать это.

Концептуально нет ничего плохого в том, чтобы иметь один пул команд для каждого потока. В самом деле, наличие двух на поток (двойная буферизация) имеет еще больший смысл.

Я лично не знаю, почему бы вам просто не записать все заранее.

Потому что вы не делаете статическую техническую демонстрацию.

Я предполагаю, что это происходит из-за недостатка опыта, но я представил, что параллельная запись будет выглядеть как «потоки 2-N записывают вторичные буферы команд, поток 1 вызывает их все в одном первичном буфере команд», и в этом случае есть только одна команда буфер на поток. Вот почему я сказал, что это убивает цель пулов команд, потому что вы делаете только одно распределение для каждого пула.

Это, безусловно, жизнеспособная форма параллельной записи буферов команд. Но вы упустили две вещи.

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

И даже если бы это было не так, вы забываете о буферизации. Когда приходит время создавать CB для следующего кадра, вы не можете просто перезаписать существующие. В конце концов, они, вероятно, все еще в очереди на работу. Таким образом, каждому потоку потребуется как минимум два CB; тот, который в настоящее время выполняется, и тот, который в настоящее время строится.

И даже если это не так, пулы команд выделяют всю память, связанную с CB. Есть причина, по которой я сравнил их с malloc/free. Даже если вы используете только один CB с определенным пулом, тот факт, что выделения этого CB (что может произойти из-за любой vkCmd* функции) никогда не должны синхронизироваться с другим потоком, - это хорошо.

Так что нет, это никоим образом не препятствует возможности использования нескольких потоков для построения CB.

person Nicol Bolas    schedule 12.07.2016
comment
Опечатка в моем сообщении: вся цель команд пулов, извините за это. - person Shahbaz; 12.07.2016
comment
Я предполагаю, что это происходит из-за недостатка опыта, но я предполагал, что параллельная запись будет выглядеть как потоки 2-N записывают вторичные буферы команд, поток 1 вызывает их все в одном первичном буфере команд, и в этом случае на каждый буфер приходится только один командный буфер. нить. Вот почему я сказал, что это убивает цель пулов команд, потому что вы делаете только одно распределение для каждого пула. - person Shahbaz; 12.07.2016
comment
@Shahbaz: Ваша опечатка не повлияла на мой ответ. - person Nicol Bolas; 12.07.2016
comment
[...] тот факт, что выделения этого CB [...] никогда не должны синхронизироваться с другим потоком, - это хорошо. Но так ли это? В какой-то момент пул команд, который по сути является диспетчером памяти для создаваемых им буферов команд, должен захватить больше памяти хоста. Это означает, что необходимо синхронизировать по крайней мере распределитель памяти хоста, верно? В противном случае я не понимаю, как пулы команд могут работать независимо от других. В стандартном случае это будет malloc, который, как я полагаю, синхронизирован, но вам все равно придется вручную синхронизировать при предоставлении настраиваемого распределителя. Правильно? - person Manuzor; 29.10.2016
comment
@Manuzor: вам все равно нужно вручную синхронизировать при предоставлении настраиваемого распределителя. Верно? ... а? Выделения, ограниченные объектом пула команд, не должны синхронизироваться. Поэтому, если хотите, вы можете создать распределитель, который минимизирует такую ​​синхронизацию, ограничивая ее исключительно получением страниц ОЗУ из ОС. - person Nicol Bolas; 30.10.2016
comment
@NicolBolas Вот что я имел в виду. На каком-то уровне нужно синхронизировать выделение памяти. Я просто хотел прояснить (в основном для себя), что использование пулов команд в потоках само по себе не освобождает от синхронизации, а скорее сводит к минимуму. Особенно по сравнению с ручной синхронизацией каждой операции с командным буфером (ой!). Кстати, спасибо за быстрый ответ! - person Manuzor; 30.10.2016
comment
@Manuzor, вы оплачиваете синхронизацию один раз, когда выделяете память пула (возможно, автоматически выполняется в malloc, если она используется, или автоматически выполняется ядром, например, если они просто получают страницы), но дело в том, что вы этого не делаете требуется синхронизация во время критического по времени выполнения. - person Shahbaz; 19.10.2017

Если вы собираетесь вести параллельную запись, вам лучше иметь отдельный пул для каждого потока, не так ли?

Совершенно верно. Это то, что подразумевает ваша цитата из спецификации.

Я бы понял это, если бы вы предварительно записали буферы команд, выделенные все из одного пула (в одном потоке), а затем выполняли их параллельно.

У Вулкана получается лучше. Вы можете предварительно записать командные буферы (выделенные из пулов потоков) параллельно и затем выполнять их также параллельно (если ваша рабочая нагрузка способствует этому).

Я лично не знаю, почему бы вам просто не записать все заранее. Так почему же так необходимо параллельное создание командных буферов?

Потому что это сложно (особенно когда ваше приложение становится все сложнее). В какой-то момент даже контрпродуктивно (когда вы перекручиваете CmB для предварительной записи - например, заполняете его пустыми привязками-заполнителями, из которых 80% из них не будут использоваться).
Это не обязательно «необходимо», Vulkan просто позволяет вам выбрать то, что, по вашему мнению, лучше всего подходит для вашего приложения (или его части).

person krOoze    schedule 12.07.2016
comment
Это необязательно, Vulkan просто дает вам дополнительную возможность. Более того, Vulkan позволяет вам выбирать. Вы можете предварительно записать некоторые вещи (фильтры постобработки, представление изображений и т. Д.), Динамически заполняя другие. - person Nicol Bolas; 12.07.2016