Во-первых, необязательно применять дизайн, ориентированный на данные, везде. В конечном итоге это оптимизация, и даже в критически важной для производительности кодовой базе по-прежнему есть множество частей, которые от этого не выигрывают.
Я часто думаю об этом как о стирающей структуре в пользу больших блоков данных, которые более эффективно обрабатывать. Возьмем, к примеру, изображение. Для эффективного представления своих пикселей обычно требуется хранить простой массив числовых значений, а не, скажем, коллекцию определенных пользователем абстрактных пиксельных объектов, которые в качестве преувеличенного примера имеют виртуальный указатель.
Представьте себе 4-компонентное (RGBA) 32-битное изображение, использующее числа с плавающей запятой, но использующее только 8-битную альфа-канал по какой-либо причине (извините, это своего рода глупый пример). Если бы мы даже использовали базовый struct
для типа пикселя, обычно нам потребовалось бы значительно больше памяти, используя структуру пикселей из-за заполнения структуры, необходимого для выравнивания.
struct Image
{
struct Pixel
{
float r;
float g;
float b;
unsigned char alpha;
// some padding (3 bytes, e.g., assuming 32-bit alignment
// for floats and 8-bit alignment for unsigned char)
};
vector<Pixel> Pixels;
};
Даже в этом простом случае превращение его в плоский массив с плавающей запятой с параллельным массивом из 8-битных альфа-каналов уменьшает размер памяти и в результате потенциально повышает скорость последовательного доступа.
struct Image
{
vector<float> rgb;
vector<unsigned char> alpha;
};
... и именно так мы должны думать изначально: о данных, схемах памяти. Конечно, изображения обычно уже представлены эффективно, и алгоритмы обработки изображений уже реализованы для обработки большого количества пикселей в большом количестве.
Тем не менее, дизайн, ориентированный на данные, выводит это на более высокий уровень, чем обычно, применяя такое представление даже к вещам, которые находятся на значительно более высоком уровне, чем пиксель. Подобным образом вы можете получить выгоду от моделирования ParticleSystem
вместо одного Particle
, чтобы оставить передышку для оптимизации, или даже People
вместо Person
.
Но вернемся к примеру с изображением. Это может означать отсутствие Министерства обороны США:
struct Image
{
struct Pixel
{
// Adjust the brightness of this pixel.
void adjust_brightness(float amount);
float r;
float g;
float b;
};
vector<Pixel> Pixels;
};
Проблема этого adjust_brightness
метода в том, что он разработан с точки зрения интерфейса для работы с одним пикселем. Это может затруднить применение оптимизаций и алгоритмов, которые выигрывают от одновременного доступа к нескольким пикселям. Между тем, примерно так:
struct Image
{
vector<float> rgb;
};
void adjust_brightness(Image& img, float amount);
... можно записать таким образом, чтобы получить доступ к нескольким пикселям одновременно. Мы могли бы даже представить это таким образом с помощью представителя SoA:
struct Image
{
vector<float> r;
vector<float> g;
vector<float> b;
};
... что может быть оптимальным, если ваши горячие точки связаны с последовательной обработкой. Детали не имеют большого значения. Для меня важно то, что ваш дизайн оставляет передышку для оптимизации. Для меня значение DOD на самом деле состоит в том, как предварительное рассмотрение такого рода мысли даст вам такие типы интерфейсов, которые оставляют вам передышку для дальнейшей оптимизации по мере необходимости без навязчивых изменений дизайна.
Полиморфизм
Классический пример полиморфизма также имеет тенденцию сосредотачиваться на этом гранулярном мышлении «одно за другим», например, Dog
наследует Mammal
. В играх, которые иногда могут приводить к возникновению узких мест, когда разработчикам приходится бороться с системой типов, сортировка полиморфных базовых указателей по подтипам для улучшения временной локализации в vtable, попытка сделать данные определенного подтипа (например, Dog
), непрерывно распределенных с помощью пользовательских распределители для улучшения пространственной локализации для каждого экземпляра подтипа и т. д.
Ничего из этого не должно быть, если мы моделируем на более грубом уровне. Вы можете Dogs
наследовать абстрактное Mammals
. Теперь стоимость виртуальной отправки снижена до одного раза на тип млекопитающего, а не один раз на млекопитающее, и все млекопитающие определенного типа могут быть представлены эффективно и непрерывно.
Вы все еще можете использовать ООП и полиморфизм с мышлением Министерства обороны США. Хитрость заключается в том, чтобы убедиться, что вы проектируете вещи на достаточно грубом уровне, чтобы вы не пытались бороться с системой типов и обходить типы данных, чтобы восстановить контроль над такими вещами, как макеты памяти. Вам не придется возиться ни с чем из этого, если вы проектируете вещи на достаточно грубом уровне.
Дизайн интерфейса
Есть еще дизайн интерфейса, связанный с DOD, по крайней мере, насколько я понимаю, и вы можете иметь методы в своих классах. По-прежнему очень важно разрабатывать правильные высокоуровневые интерфейсы, и вы по-прежнему можете использовать виртуальные функции и шаблоны и работать очень абстрактно. Практическое отличие, на котором я хотел бы сосредоточиться, заключается в том, что вы разрабатываете агрегированные интерфейсы, как в случае adjust_brightness
метода выше, что оставляет вам передышку для оптимизации без каскадных изменений дизайна во всей кодовой базе. Мы разрабатываем интерфейс для обработки нескольких пикселей всего изображения вместо одного, который обрабатывает один пиксель за раз.
Проекты интерфейсов DOD часто предназначены для массовой обработки и, как правило, с оптимальной компоновкой памяти для наиболее критичных к производительности последовательных циклов линейной сложности, которые должны иметь доступ ко всему.
Итак, если мы возьмем ваш пример с Model
, чего не хватает, так это агрегированной стороны интерфейса.
struct Models {
// Methods to process models in bulk can go here.
struct Model {
// vertex buffers
GLuint Positions, Normals, Texcoords, Elements;
// textures
GLuint Diffuse, Normal, Specular;
// further material properties
GLfloat Shininess;
};
std::vector<Model> models;
};
Это не обязательно должно быть представлено с использованием класса с методами. Это может быть функция, которая принимает массив structs
. Эти детали на самом деле не имеют большого значения, важно то, что интерфейс в основном предназначен для последовательной массовой обработки, в то время как представление данных разработано оптимально для этого случая.
Горячее / холодное разделение
Глядя на ваш Person
класс, вы все еще можете думать в некотором роде в стиле классического интерфейса (хотя интерфейс здесь - это просто данные). Опять же, DOD в первую очередь будет использовать struct
для всего, только если это будет оптимальная конфигурация памяти для наиболее критичных к производительности циклов. Это не о логической организации для людей, а об организации данных для машин.
struct Person {
Person() : Walking(false), Jumping(false) {}
float Height, Mass;
bool Walking, Jumping;
};
Сначала давайте поместим это в контекст:
struct People {
struct Person {
Person() : Walking(false), Jumping(false) {}
float Height, Mass;
bool Walking, Jumping;
};
};
В этом случае часто ли обращаются ко всем полям вместе? Предположим, гипотетически ответ отрицательный. Эти поля Walking
и Jumping
доступны только иногда (холодно), в то время как к Height
и Mass
обращаются постоянно (горячо). В этом случае потенциально более оптимальным представлением может быть:
struct People {
vector<float> HeightMass;
vector<bool> WalkingJumping;
};
Конечно, вы можете создать здесь две отдельные структуры, соединить одну точку с другой и т. Д. Ключ в том, что вы разрабатываете это в конечном итоге с точки зрения макета памяти / производительности, а в идеале с хорошим профилировщиком в руках и твердым пониманием общие пути кода на стороне пользователя.
С точки зрения интерфейса, вы разрабатываете интерфейс с упором на обработку людей, а не человека.
Проблема
Разобравшись с этим, перейдем к вашей проблеме:
Я могу создавать модели только из этого модуля, а не из других. Должен ли я переместить это в класс типа Model при его усложнении?
Это скорее проблема дизайна подсистемы. Поскольку ваш Model
представитель полностью посвящен данным OpenGL, он, вероятно, должен принадлежать модулю, который может правильно инициализировать / уничтожить / отобразить его. Это может быть даже частная / скрытая деталь реализации этого модуля, и в этот момент вы применяете мышление Министерства обороны США в рамках реализации модуля.
Однако интерфейс, доступный внешнему миру для добавления моделей, уничтожения моделей, их рендеринга и т. Д., В конечном итоге должен быть разработан для массового использования. Думайте об этом как о разработке высокоуровневого интерфейса для контейнера, в котором методы, которые вы можете добавить для каждого элемента, вместо этого будут принадлежать контейнеру, как в нашем примере изображения выше с adjust_brightness
.
Сложная инициализация / уничтожение часто требует индивидуального подхода к проектированию, но главное в том, что вы делаете это через агрегированный интерфейс. Здесь вы все равно можете отказаться от стандартного конструктора и деструктора для Model
в пользу инициализации при добавлении Model
GPU для рендеринга, очистки ресурсов GPU при удалении его из списка. Это отчасти возвращается к кодированию в стиле C для отдельного типа (например, человека), хотя вы все равно можете получить очень изощренные возможности с помощью C ++ для совокупного интерфейса (например, людей).
У меня вопрос: следует ли добавлять методы в классы типов?
В основном дизайн для больших объемов, и вы должны быть в пути. В приведенных вами примерах обычно нет. Это не должно быть самым сложным правилом, но ваши типы моделируют отдельные вещи, и для того, чтобы оставить место для Министерства обороны США, часто требуется уменьшение масштаба и разработка интерфейсов, которые имеют дело со многими вещами.
person
Community
schedule
29.11.2015
bool Walking, Jumping;
). Вы используетеunordered_map
, когда DOD использует массивы (в основном везде). Использование классов, наследования иprivate
полей также похоже на DOD. И нет, я думаю, вам не следует никуда добавлять методы, потому что методы означают, что в этом экземпляре нужно что-то делать !. В министерстве обороны вы думаете, что в некоторых случаях давайте сделаем что-то одно! - person cubuspl42   schedule 09.03.2014