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