Настроить
У меня есть объектно-компонентная архитектура, в которой сущности могут иметь набор атрибутов (которые являются чистыми данными без поведения), и существуют системы, которые выполняют логику сущностей, которая воздействует на эти данные. По сути, в некотором псевдокоде:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
Система, которая просто движется по всем объектам с постоянной скоростью, может быть
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
По сути, я пытаюсь распараллелить update () максимально эффективно. Это можно сделать, запустив целые системы параллельно или предоставив каждому update () одной системы пару компонентов, чтобы разные потоки могли выполнять обновление одной и той же системы, но для другого подмножества объектов, зарегистрированных в этой системе.
проблема
В случае показанной MovementSystem распараллеливание тривиально. Поскольку объекты не зависят друг от друга и не изменяют общие данные, мы можем просто переместить все объекты параллельно.
Однако эти системы иногда требуют, чтобы объекты взаимодействовали (считывали / записывали данные из / в) друг с другом, иногда в одной и той же системе, но часто между различными системами, которые зависят друг от друга.
Например, в физической системе иногда сущности могут взаимодействовать друг с другом. Два объекта сталкиваются, их положения, скорости и другие атрибуты считываются из них, обновляются, а затем обновленные атрибуты записываются обратно в оба объекта.
И прежде чем система рендеринга в движке сможет начать рендеринг сущностей, она должна подождать, пока другие системы завершат выполнение, чтобы убедиться, что все соответствующие атрибуты являются такими, какими они должны быть.
Если мы попытаемся слепо распараллелить это, это приведет к классическим гоночным условиям, когда разные системы могут одновременно считывать и изменять данные.
В идеале, должно существовать решение, при котором все системы могут считывать данные из любых сущностей, которые они хотят, не беспокоясь о том, что другие системы изменяют эти же данные одновременно, и не заботясь о том, чтобы программист заботился о правильном порядке выполнения и распараллеливания эти системы вручную (что иногда даже невозможно).
В базовой реализации этого можно достичь, просто поместив все данные для чтения и записи в критические секции (защищая их мьютексами). Но это вызывает большие накладные расходы времени выполнения и, вероятно, не подходит для приложений, чувствительных к производительности.
Решение?
На мой взгляд, возможным решением будет система, в которой чтение / обновление и запись данных разделены, так что на одном дорогостоящем этапе системы только читают данные и вычисляют то, что им нужно для вычисления, каким-то образом кэшируют результаты, а затем записывают все измененные данные возвращаются к целевым объектам в отдельном проходе записи. Все системы будут работать с данными в том состоянии, в котором они находились в начале кадра, а затем до конца кадра, когда все системы завершат обновление, происходит сериализованный этап записи, когда результаты кэширования получаются из всех разных системы повторяются и записываются обратно в целевые объекты.
Это основано на (возможно, неправильной?) Идее, что выигрыш в простом распараллеливании может быть достаточно большим, чтобы превзойти стоимость (как с точки зрения производительности во время выполнения, так и накладных расходов кода) кэширования результатов и прохода записи.
Вопрос
Как такая система может быть реализована для достижения оптимальной производительности? Каковы детали реализации такой системы и каковы предпосылки для системы Entity-Component, которая хочет использовать это решение?
источник
Я слышал об интересном решении этой проблемы: идея состоит в том, что будет 2 копии данных сущности (я знаю, что это расточительно). Одна копия будет настоящей копией, а другая - прошлой. Настоящая копия предназначена только для записи, а последняя - только для чтения. Я предполагаю, что системы не хотят записывать в одни и те же элементы данных, но если это не так, эти системы должны быть в одном потоке. Каждый поток будет иметь доступ на запись к текущим копиям взаимоисключающих разделов данных, а каждый поток имеет доступ на чтение ко всем прошлым копиям данных и, таким образом, может обновлять существующие копии, используя данные из прошлых копий без замок. Между каждым кадром текущая копия становится прошлой, однако вы хотите справиться с обменом ролями.
Этот метод также удаляет условия гонки, потому что все системы будут работать с устаревшим состоянием, которое не изменится до / после того, как система его обработала.
источник
Я знаю 3 проекта программного обеспечения, обрабатывающих параллельную обработку данных:
Вот несколько примеров для каждого подхода, который может использоваться в системе сущностей:
CollisionSystem
том, что читаетPosition
иRigidBody
компоненты и должны обновитьVelocity
. Вместо того, чтобы манипулироватьVelocity
непосредственно,CollisionSystem
завещание вместо этого помещаетCollisionEvent
в рабочую очередь объектаEventSystem
. Это событие будет затем обрабатываться последовательно с другими обновлениямиVelocity
.EntitySystem
определяет набор компонентов, которые он должен прочитать и записать. Для каждогоEntity
он получит блокировку чтения для каждого компонента, который он хочет прочитать, и блокировку записи для каждого компонента, который он хочет обновить. Таким образом, каждыйEntitySystem
сможет одновременно считывать компоненты во время синхронизации операций обновления.MovementSystem
,Position
компонент является неизменным и содержит номер редакции .MovementSystem
Савелий читаетPosition
иVelocity
компонент и вычисляет новыйPosition
, увеличивающееся считанные ревизии номера и пытаются обновляемым вPosition
компонент. В случае одновременного изменения инфраструктура указывает это при обновлении, и оноEntity
будет возвращено в список объектов, которые должны быть обновленыMovementSystem
.В зависимости от систем, объектов и интервалов обновления каждый подход может быть хорошим или плохим. Инфраструктура системы сущностей может позволить пользователю выбирать между этими опциями для настройки производительности.
Я надеюсь, что смогу добавить некоторые идеи к обсуждению и, пожалуйста, дайте мне знать, если есть какие-то новости об этом.
источник