Что такое ориентированный на данные дизайн?
Я читал эту статью, и этот парень продолжает рассказывать о том, как каждый может получить большую выгоду от смешения в ориентированном на данные дизайне с ООП. Однако он не показывает никаких примеров кода.
Я погуглил это и не смог найти никакой реальной информации о том, что это такое, не говоря уже о примерах кода. Кто-нибудь знаком с этим термином и может привести пример? Это может быть другое слово для чего-то еще?
6 ответов
Прежде всего, не путайте это с дизайном, управляемым данными.
Я понимаю, что Data Oriented Design - это организация ваших данных для эффективной обработки. Особенно в отношении пропусков кэша и т. Д. С другой стороны, Data Driven Design - это то, что позволяет данным контролировать поведение многих ваших программ (очень хорошо описано в ответе Эндрю Кейта).
Допустим, в вашем приложении есть шариковые объекты со свойствами, такими как цвет, радиус, упругость, положение и т. Д.
Объектно-ориентированный подход
В ООП вы бы описали свои шары так:
class Ball {
Point position;
Color color;
double radius;
void draw();
};
И тогда вы создадите коллекцию шаров, как это:
vector<Ball> balls;
Ориентированный на данные подход
Однако в Data Oriented Design вы с большей вероятностью напишите такой код:
class Balls {
vector<Point> position;
vector<Color> color;
vector<double> radius;
void draw();
};
Как вы можете видеть, больше нет единой единицы, представляющей один мяч. Объекты шара существуют только неявно.
Это может иметь много преимуществ в отношении производительности. Обычно мы хотим выполнять операции на нескольких шарах одновременно. Аппаратные средства обычно хотят, чтобы большие непрерывные порции памяти работали эффективно.
Во-вторых, вы можете выполнять операции, которые затрагивают только часть свойств шариков. Например, если вы комбинируете цвета всех шаров различными способами, то вы хотите, чтобы ваш кеш содержал только информацию о цвете. Однако, когда все свойства шара сохранены в одной единице, вы также включите все другие свойства шара. Даже если они вам не нужны.
Пример использования кэша
Скажем, каждый мяч занимает 64 байта, а точка занимает 4 байта. Слот для кеша занимает, скажем, 64 байта. Если я хочу обновить позицию 10 шаров, я должен вытянуть 10*64 = 640 байт памяти в кеш и получить 10 промахов в кеше. Однако, если я могу работать с позициями шаров как с отдельными единицами, это займет всего 4*10 = 40 байт. Это помещается в одну выборку из кэша. Таким образом, мы получаем только 1 промах кэша, чтобы обновить все 10 шаров. Эти числа являются произвольными, я предполагаю, что блок кэша больше.
Но это иллюстрирует, как разметка памяти может иметь серьезные последствия для попадания в кэш и, следовательно, на производительность. Это только увеличится в важности, поскольку разница между скоростью процессора и оперативной памяти увеличивается.
Как расположить память
В моем примере с мячом я значительно упростил задачу, потому что обычно для любого обычного приложения вы, вероятно, будете обращаться к нескольким переменным вместе. Например, положение и радиус, вероятно, будут часто использоваться вместе. Тогда ваша структура должна быть:
class Body {
Point position;
double radius;
};
class Balls {
vector<Body> bodies;
vector<Color> color;
void draw();
};
Причина, по которой вам следует это сделать, заключается в том, что если данные, используемые вместе, помещаются в отдельные массивы, существует риск, что они будут конкурировать за одни и те же слоты в кэше. Таким образом, загрузка одного выбросит другого.
Таким образом, по сравнению с объектно-ориентированным программированием классы, которые вы в итоге делаете, не связаны с сущностями в вашей ментальной модели проблемы. Поскольку данные объединяются в единое целое на основе использования данных, у вас не всегда будут разумные имена для ваших классов в Data Oriented Design.
Отношение к реляционным базам данных
Концепция Data Oriented Design очень похожа на то, как вы думаете о реляционных базах данных. Оптимизация реляционной базы данных также может включать более эффективное использование кэша, хотя в этом случае кэш-память процессора не помещает страницы в память. Хороший конструктор баз данных также, скорее всего, будет разбивать редко используемые данные на отдельные таблицы, а не создавать таблицы с огромным количеством столбцов, в которых когда-либо использовались только некоторые из столбцов. Он может также выбрать денормализацию некоторых таблиц, чтобы к данным не приходилось обращаться из нескольких мест на диске. Как и в случае с Data-Oriented Design, этот выбор делается при рассмотрении того, что такое шаблоны доступа к данным и где узкое место в производительности.
Майк Актон недавно выступил с публичной речью о Дата-ориентированном дизайне:
Я подытожу это следующим образом: если вы хотите повысить производительность, подумайте о потоке данных, найдите уровень хранения, который, скорее всего, вам не по душе, и жестко оптимизируйте его . Майк сосредотачивается на промахах L2-кэша, потому что он работает в режиме реального времени, но я думаю, что то же самое относится и к базам данных (чтение с диска) и даже к сети (HTTP-запросы). Я думаю, это полезный способ системного программирования.
Обратите внимание, что это не освобождает вас от размышлений об алгоритмах и сложности времени, а просто сосредотачивает ваше внимание на определении самого дорогого типа операций, на который вы затем должны нацелиться, используя свои безумные навыки CS.
Я просто хочу отметить, что Ноэль говорит конкретно о некоторых специфических потребностях, с которыми мы сталкиваемся при разработке игр. Я полагаю, что другие сектора, которые выполняют мягкое моделирование в реальном времени, выиграют от этого, но вряд ли это будет метод, который покажет заметное улучшение для общих бизнес-приложений. Эта настройка предназначена для того, чтобы гарантировать, что каждый последний бит производительности выжимается из базового оборудования.
Ориентированный на данные проект - это проект, в котором логика приложения состоит из наборов данных, а не процедурных алгоритмов. Например
процедурный подход.
int animation; // this value is the animation index
if(animation == 0)
PerformMoveForward();
else if(animation == 1)
PerformMoveBack();
.... // etc
подход к проектированию данных
typedef struct
{
int Index;
void (*Perform)();
}AnimationIndice;
// build my animation dictionary
AnimationIndice AnimationIndices[] =
{
{ 0,PerformMoveForward }
{ 1,PerformMoveBack }
}
// when its time to run, i use my dictionary to find my logic
int animation; // this value is the animation index
AnimationIndices[animation].Perform();
Подобные конструкции данных способствуют использованию данных для построения логики приложения. Управлять им проще, особенно в видеоиграх, которые могут иметь тысячи логических путей на основе анимации или какого-либо другого фактора.
Если вы хотите воспользоваться преимуществами современной архитектуры процессора, вам необходимо определенным образом разместить свои данные в памяти. ЦП действительно хороши в обработке простых типов, которые последовательно размещаются в памяти. Любой другой макет имеет гораздо более высокую стоимость обработки.
В объектно-ориентированном подходе вы всегда думаете об одном экземпляре, а затем расширяете его до нескольких экземпляров, группируя объекты в коллекции. Но с точки зрения оборудования это связано с дополнительными расходами.
В подходе, ориентированном на данные, у вас нет «экземпляра», как в объектно-ориентированном программировании. Ваш экземпляр может иметь идентификатор, аналогичный данным в реляционных базах данных, но помимо этого данные, относящиеся к вашему экземпляру, могут быть разделены на несколько таблиц (таблицы реализованы как векторы), чтобы обеспечить эффективную обработку.
Пример: представьте, что у вас есть класс Student {int id; std::string name; плавающий средний; bool закончил; }. В случае ООП вы поместите всех своих учеников в один вектор.
При проектировании, ориентированном на данные, вы сначала спросите себя, какую обработку вы хотите произвести с этими данными. Допустим, вы хотите рассчитать средний балл для всех студентов, которые еще не окончили школу. Таким образом, вы создадите таблицу, которая будет содержать только студентов, которые закончили обучение, и еще одну, которая еще не закончила. Вы не будете хранить имя студента в этой таблице, поскольку оно не используется для обработки. Но вы сохраните студенческий билет и среднюю оценку в таблице.
Теперь вычисление среднего балла для студентов без диплома будет означать итерацию по таблице без диплома и выполнение расчета. Поскольку средние отметки в памяти находятся рядом, ваш ЦП будет использовать SIMD и обрабатывать данные наиболее эффективным способом. Поскольку мы не запрашиваем bool Graduated, чтобы проверить, окончил ли студент обучение, промахов в кэше данных не возникает.
В теории это звучит хорошо, но я никогда не занимался подобной разработкой в реальных проектах. Если у кого-то есть опыт, напишите мне, у меня много вопросов.
Впервые я услышал о Data-Oriented Design в подкасте Our Machinery, эпизод «S3: EP4 Data-Oriented Design». https://www.owltail.com/podcast/Atvr2-Our-Machinery
Возможно, что-то изменилось, но некоторое время назад найти информацию о дизайне, ориентированном на данные, было сложно. Единственная книга, которую я нашел, это: https://www.manning.com/books/data-Oriented-programming .