Първо, не е задължително да прилагате ориентиран към данни дизайн навсякъде. В крайна сметка това е оптимизация и дори критичната за производителността кодова база все още има много части, които не се възползват от нея.
Склонен съм често да мисля за това като заличаваща структура в полза на големи блокове от данни, които са по-ефективни за обработка. Вземете изображение, например. Ефективното представяне на неговите пиксели обикновено изисква съхраняване на прост масив от числови стойности, а не, да речем, колекция от дефинирани от потребителя абстрактни пикселни обекти, които имат виртуален указател като преувеличен пример.
Представете си 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
.
Но да се върнем към примера с изображение. Това би означавало липса на DOD:
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. Номерът е да се уверите, че проектирате нещата на достатъчно грубо ниво, така че да не се опитвате да се борите срещу системата от типове и да работите около типовете данни, за да си възвърнете контрола върху неща като оформление на паметта. Няма да се налага да се занимавате с нищо от това, ако проектирате нещата на достатъчно грубо ниво.
Дизайн на интерфейс
Все още има дизайн на интерфейс, включен в 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
rep е изцяло за OpenGL данни, той вероятно трябва да принадлежи към модула, който може правилно да го инициализира/унищожи/рендерира. Може дори да е частен/скрит детайл за изпълнение на този модул, в който момент прилагате DOD начин на мислене в изпълнението на модула.
Интерфейсът, достъпен за външния свят за добавяне на модели, унищожаване на модели, изобразяване и т.н., обаче, в крайна сметка трябва да бъде проектиран за масово. Мислете за това като за проектиране на интерфейс на високо ниво за контейнер, където методите, които бихте се изкушили да добавите за всеки елемент, вместо това в крайна сметка принадлежат на контейнера, както в нашия пример с изображение по-горе с adjust_brightness
.
Сложната инициализация/унищожаване често се нуждае от манталитет на проектиране един по един, но ключът е, че правите това чрез общ интерфейс. Тук все пак може да се откажете от стандартния конструктор и деструктор за Model
в полза на инициализиране при добавяне на GPU Model
за изобразяване, почистване на ресурсите на GPU при премахването му от списъка. Донякъде се връща към кодирането в стил C за индивидуалния тип (човек, напр.), въпреки че все още можете да станете много сложни с екстри на C++ за общия интерфейс (хора, напр.).
Въпросът ми е трябва ли да добавя методи към моите типови класове?
Основно дизайн за насипно състояние и трябва да сте на път. В примерите, които показахте, обикновено не. Не е нужно да е най-трудното правило, но вашите типове моделират отделни неща и за да оставите място за DOD често се налага намаляване на мащаба и проектиране на интерфейси, които се занимават с много неща.
person
Community
schedule
29.11.2015
bool Walking, Jumping;
). Използватеunordered_map
, когато DOD е за използване на масиви (предимно навсякъде). Използването на класове, наследяване иprivate
полета също изглежда е против DOD. И не, мисля, че не трябва да добавяте методи никъде, защото методите означават да правите някои неща в този екземпляр!. В DOD мислите, че нека направим едно нещо в някои случаи!. - person cubuspl42   schedule 09.03.2014