Primeiro de tudo, não confunda isso com design orientado a dados.
Meu entendimento do Design Orientado a Dados é que se trata de organizar seus dados para um processamento eficiente. Especialmente com relação a falhas de cache, por outro lado, o Data Driven Design é sobre permitir que os dados controlem grande parte do comportamento de seus programas (descrito muito bem pela resposta de Andrew Keith ).
Digamos que você tenha objetos de bola em seu aplicativo com propriedades como cor, raio, bounciness, posição etc.
Abordagem Orientada a Objetos
No OOP, você descreveria bolas como esta:
class Ball {
Point position;
Color color;
double radius;
void draw();
};
E então você criaria uma coleção de bolas como esta:
vector<Ball> balls;
Abordagem Orientada a Dados
No Design Orientado a Dados, no entanto, é mais provável que você escreva o código assim:
class Balls {
vector<Point> position;
vector<Color> color;
vector<double> radius;
void draw();
};
Como você pode ver, não há mais uma unidade representando uma bola. Objetos de bola existem apenas implicitamente.
Isso pode ter muitas vantagens, em termos de desempenho. Normalmente, queremos fazer operações em muitas bolas ao mesmo tempo. O hardware geralmente deseja que grandes pedaços contínuos de memória operem com eficiência.
Em segundo lugar, você pode executar operações que afetam apenas parte das propriedades de uma bola. Por exemplo, se você combinar as cores de todas as bolas de várias maneiras, deseja que seu cache contenha apenas informações de cores. No entanto, quando todas as propriedades da bola estiverem armazenadas em uma unidade, você puxará todas as outras propriedades da bola também. Mesmo que você não precise deles.
Exemplo de uso de cache
Digamos que cada bola ocupa 64 bytes e um ponto ocupa 4 bytes. Um slot de cache ocupa, digamos, 64 bytes também. Se eu quiser atualizar a posição de 10 bolas, preciso extrair 10 * 64 = 640 bytes de memória para o cache e obter 10 erros de cache. Se, no entanto, eu puder trabalhar as posições das bolas como unidades separadas, isso levará apenas 4 * 10 = 40 bytes. Isso se encaixa em uma busca de cache. Assim, temos apenas 1 falta de cache para atualizar todas as 10 bolas. Esses números são arbitrários - presumo que um bloco de cache seja maior.
Mas ilustra como o layout da memória pode ter um efeito severo nos acertos do cache e, portanto, no desempenho. Isso só aumentará em importância à medida que a diferença entre a velocidade da CPU e da RAM aumentar.
Como organizar a memória
No meu exemplo de bola, simplifiquei bastante o problema, porque geralmente para qualquer aplicativo normal você provavelmente acessará várias variáveis juntas. Por exemplo, posição e raio provavelmente serão usados juntos com frequência. Então sua estrutura deve ser:
class Body {
Point position;
double radius;
};
class Balls {
vector<Body> bodies;
vector<Color> color;
void draw();
};
O motivo para você fazer isso é que, se os dados usados juntos forem colocados em matrizes separadas, existe o risco de que eles concorram pelos mesmos slots no cache. Assim, carregar um jogará fora o outro.
Portanto, comparadas à programação orientada a objetos, as classes que você acaba fazendo não estão relacionadas às entidades em seu modelo mental do problema. Como os dados são agrupados com base no uso de dados, você nem sempre terá nomes sensíveis para dar suas aulas no Design Orientado a Dados.
Relação com bancos de dados relacionais
O pensamento por trás do Design Orientado a Dados é muito parecido com o que você pensa sobre bancos de dados relacionais. A otimização de um banco de dados relacional também pode envolver o uso mais eficiente do cache, embora, nesse caso, o cache não seja o cache da CPU, mas as páginas na memória. Um bom designer de banco de dados também provavelmente dividirá os dados acessados com pouca frequência em uma tabela separada, em vez de criar uma tabela com grande número de colunas, onde apenas algumas das colunas foram usadas. Ele também pode optar por desnormalizar algumas das tabelas para que os dados não precisem ser acessados de vários locais no disco. Assim como no Design Orientado a Dados, essas escolhas são feitas observando quais são os padrões de acesso a dados e onde está o gargalo de desempenho.