Ответ 1
Итак, что же такое DOD? Очевидно, это о производительности, но это не только это. Это также о хорошо разработанном коде, который читабелен, прост для понимания и даже для повторного использования. Теперь объектно-ориентированное проектирование - это все о разработке кода и данных для встраивания в инкапсулированные виртуальные "объекты". Каждый объект представляет собой отдельную сущность с переменными для свойств, которые объект может иметь, и методы для действий над собой или другими объектами в мире. Преимущество ОО-дизайна в том, что он легко мысленно моделирует ваш код в объекты, потому что весь (реальный) мир вокруг нас, кажется, работает одинаково. Объекты со свойствами, которые могут взаимодействовать друг с другом.
Теперь проблема в том, что процессор на вашем компьютере работает совершенно по-другому. Это работает лучше всего, когда вы позволяете ему делать то же самое снова и снова. Почему это? Из-за мелочи под названием кеш. Доступ к ОЗУ на современном компьютере может занять 100 или 200 циклов ЦП (а ЦП должен ждать все это время!), Что слишком долго. Таким образом, на процессоре есть небольшая часть памяти, к которой можно быстро получить доступ, кэш-память. Проблема в том, что это всего лишь несколько мегабайт. Таким образом, каждый раз, когда вам нужны данные, которых нет в кеше, вам все равно нужно пройти долгий путь к оперативной памяти. Это касается не только данных, но и кода. Попытка выполнить функцию, которая не находится в кэше инструкций, приведет к остановке, пока код загружается из ОЗУ.
Вернуться к ОО программированию. Объекты большие, но большинству функций требуется лишь небольшая часть этих данных, поэтому мы тратим кэш-память, загружая ненужные данные. Методы вызывают другие методы, которые вызывают другие методы, перебивая ваш кеш инструкций. Тем не менее, мы часто делаем одно и то же снова и снова. Давайте возьмем пулю из игры, например. В наивной реализации каждая пуля может быть отдельным объектом. Там может быть класс диспетчера пуль. Вызывает первую функцию обновления пули. Он обновляет 3D-положение, используя направление/скорость. Это приводит к загрузке большого количества других данных из объекта в кэш. Затем мы вызываем класс World Manager для проверки на коллизия с другими объектами. Это загружает множество других вещей в кеш, возможно, даже вызывает сброс кода из исходного класса диспетчера маркеров из кеша команд. Теперь мы возвращаемся к обновлению маркеров, столкновений не было, поэтому мы возвращаемся к диспетчеру маркеров. Возможно, потребуется снова загрузить некоторый код. Далее, обновление № 2. Это загружает много данных в кэш, вызывает мир... и т.д. Итак, в этой гипетической ситуации у нас есть 2 киоска для загрузки кода и, скажем, 2 киоска для загрузки данных. Это по меньшей мере 400 циклов потрачено впустую, за одну пулю, и мы не приняли во внимание пули, которые попали в что-то еще. Теперь процессор работает на частоте 3+ ГГц, поэтому мы не заметим ни одной пули, но что, если у нас будет 100 пуль? Или даже больше?
Так что это там, где есть одна история. Да, есть некоторые случаи, когда у вас есть только объект, классы вашего менеджера, доступ к файлам и т.д. Но чаще встречается много подобных случаев. Наивный или даже не наивный объектно-ориентированный дизайн приведет к множеству проблем. Так что вводите ориентированный на данные дизайн. Ключом DOD является моделирование вашего кода вокруг ваших данных, а не наоборот, как с ОО-дизайном. Это начинается на первых этапах проектирования. Вы сначала не разрабатываете свой ОО-код, а затем оптимизируете его. Вы начинаете с того, что перечисляете и анализируете свои данные и продумываете, как вы хотите их изменить (я сейчас же приведу практический пример). Как только вы узнаете, как ваш код будет изменять данные, вы можете расположить их так, чтобы сделать их обработку максимально эффективной. Теперь вы можете подумать, что это может привести только к ужасному супу кода и данных повсюду, но это только в том случае, если вы плохо его проектируете (плохой дизайн так же прост с OO-программированием). Если вы спроектируете это хорошо, код и данные могут быть аккуратно спроектированы с учетом конкретной функциональности, что приведет к очень читабельному и даже очень многократному использованию кода.
Итак, вернемся к нашим пулям. Вместо того, чтобы создавать класс для каждой марки, мы оставляем только менеджер пули. У каждой пули есть позиция и скорость. Каждая позиция пули должна быть обновлена. Каждая пуля должна иметь проверку на коллизия, и все пули, попавшие во что-то, должны предпринять соответствующие действия. Поэтому, просто взглянув на это описание, я смогу спроектировать всю эту систему намного лучше. Позвольте поместить позиции всех пуль в массив/вектор. Позвольте положить скорость всех пуль в массив/вектор. Теперь давайте начнем с итерации по всем этим двум массивам и обновления каждого значения позиции с соответствующей скоростью. Теперь все данные, загруженные в кеш данных, - это данные, которые мы собираемся использовать. Мы даже можем поместить умную команду предварительной загрузки, чтобы заранее предварительно загрузить некоторые данные массива, чтобы данные находились в кэше, когда мы к нему доберемся. Далее проверка столкновения. Я не буду вдаваться в подробности, но вы можете себе представить, как может помочь обновление всех пуль друг за другом. Также обратите внимание, что в случае столкновения мы не собираемся вызывать новую функцию или что-либо делать. Мы просто сохраняем вектор со всеми пулями, у которых было коллизия, и когда проверка столкновения выполнена, мы можем обновить все те после друг друга. Посмотрите, как мы только что перешли от большого количества доступа к памяти практически к любому, выложив наши данные по-другому? Вы также заметили, что наш код и данные, хотя они и не спроектированы с использованием ОО, по-прежнему просты для понимания и повторного использования?
Итак, вернемся к тому, "где там много". При разработке ОО-кода вы думаете об одном объекте, прототипе/классе. У пули есть скорость, у пули есть позиция, пуля будет перемещаться в каждом кадре на ее скорость, пуля может попасть во что-то и т.д. Когда вы думаете об этом, вы будете думать о классе со скоростью, положением и функция обновления, которая перемещает пулю и проверяет наличие столкновений. Однако, когда у вас есть несколько объектов, вам нужно подумать обо всех них. Пули имеют позиции, скорость. У некоторых пуль может быть коллизия. Вы видите, как мы больше не думаем об отдельном объекте? Мы думаем обо всех из них и разрабатываем код совсем по-другому.
Я надеюсь, что это поможет ответить на вашу вторую часть вопроса. Речь идет не о том, нужно ли вам обновлять каждого врага или нет, а о наиболее эффективном способе их обновления. И хотя разработка только ваших врагов с использованием DOD может не помочь повысить производительность, разработка всей игры на основе этих принципов (только там, где это применимо!) Может привести к значительному увеличению производительности!
Итак, первая часть вопроса, это другие примеры DOD. Извините, но у меня там не так много. Тем не менее, есть один действительно хороший пример, с которым я столкнулся некоторое время назад, серия работ Бьорна Кнафла по ориентированному на данные проектированию дерева поведения: http://bjoernknafla.com/data-oriented-behavior-tree-overview Вы, вероятно, хотите начать с первого в серии 4, ссылки в самой статье. надеюсь, что это все еще помогает, несмотря на старый вопрос. Или, может быть, какой-то другой пользователь SO сталкивался с этим вопросом и использовал этот ответ.