Anyone else adopted data-oriented design?

Hello guys,

Is there anyone else shock-therapy-adopting Casey's style of "C++" (data-oriented design with C-like style + optional C++ features).

I'm not programming a game, but rather a GUI application trying to adopt as much as possible from data-oriented design and game development. I didn't adopt the memory allocation strategy from Handmade Hero, but I'm rather using malloc (for now). (Well as I'm binding the app directly into Lua, I'm using Lua's own allocation functions).

However malloc has same implications as the big-block alloc: not being able to use STL, or anything that is written in C++ OOP (as long as you don't want to fiddle with calling constructors and destructors yourself).

Is there anyone else doing similar thing? How are you dealing with replacing STL? Are you writing your own containers (How?) or using some C library?

Edited by Martin Cohen on
If you are OK by using malloc, why do you avoid STL? STL by default uses new/delete operators that simply directly call malloc/free. So there is no reason to avoid STL if you are OK with malloc. Same with classes - default new and delete operators call malloc/free. You can override that if you want.

Edited by Mārtiņš Možeiko on
I'm using malloc to be as-close-as-possible to big-bloc allocation that Casey does in Handmade Hero. Just to know how it feels.

So far I found it very useful to have separated allocation and initialization, especially when I'm using my code in various contexts (I'm experimenting with various libraries currently, some have their own means of allocation).

I know about overloading new/delete operators, though I haven't studied it in depth. This is what I found/deduced so far, please correct me if I'm wrong:

- Global override of new/delete will affect not only my code, but also code of any other library, or code which will use mine. I'm not sure if there's a way to isolate.
- Allocators are only used for storage of the containers, not the containers themselves.

Regards,
M.
Yes, to first.
Not sure what you mean by second. std::allocator allocate any memory container needs. You can redirect it to any pre-allocated block as you wish (similar how Casey uses PushStruct/PushArray). And if you need to store specific classes in specific memory blocks, you can override new/delete class operators (not the global ones).
I've adopted data-oriented design for some of my DSP code (audio filters and such), and I'm rather bummed that I didn't do this sooner. It's a much better fit for DSP imo than OO. Inheritance and encapsulation just got in the way, and I never even used the benefits of polymorphism that you would get through inheritance.

And like I think Casey mentioned one day on a stream, it's so much easier to parallelize and streamline your data flow when you don't go encapsulating it all.
@mmozeiko By that I meant, that allocator does not provide means of allocating the vector's own members (size, capacity, ptr, etc.), it only allows you to define how storage for elements it contains will be allocated.
What's the difference between "own members" and "storage for elements"? std::vector only allocates for its "own members", because it stores elements by value. So it doesn't allocate elements in some other storage.

So if you have:
1
2
3
4
5
struct Foo { int x; };

std::vector<Foo> FooVec;
FooVec f;
f.resize(100);

Nobody will call "new Foo" here. Vector will only allocate 400 bytes using std::allocator assigned to it, in this example the default one.
@Flyingsand I didn't need any polymorphism so far. I was experimenting with mixins, and was able to get some of that to work, but I'm still kinda waiting for Casey to show it on the stream.
@mmozeiko Wouldn't it also allocate memory on stack? It won't be allocating using "new" of course, but the vector occupies some memory that has to be allocated and initialized. On my machine in a 32-bit build it's 16bytes (3 pointers + 1 byte (aligned to 4 bytes)). That is the "own members" memory I'm referring to. And that memory cannot be allocated with custom allocator.

Anyway, back to your previous comment, I can override new/delete of my class, but I'm not aware that/how I can override new/delete only for, say, a std::vector.
What's wrong with placing vector on stack?

But it can "allocate" wherever you put it. Of course you must be careful with correctly initializing/freeing it with placement new/delete:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
typedef std::vector<int> I;

void f()
{
   I i; // on stack

   I* s = new I(); // s->i is on heap

   char* buffer = ... ; // get buffer from somewhere
   I* i = new (buffer) int I(); // put it in buffer, wherever it is, and initialize
}


If you put vector in some structure, and override new/delete operators of this structure, then you can place structure in wherever allocator you want as long new/delete of this structure initializes vector. Typically this is what you want. Because you want to keep members of structure together.
@mmozeiko Yes, I was using the placement before. However I have been calling it from my "init" functions, to keep the allocation and initialization separate. Then I have decided to try to go without it (and therefore without STL). However, I like the idea with new/delete, I'll give it a try. Thank you!
I find the STL to be:

A. Too verbose,

B. Difficult to debug when it goes wrong due to the excessive templatization (and/or has bugs in it, which happens surprisingly often it seems),

C. Has a seriously negative effect on compilation time and output file sizes (esp. debug info) which in turn is a big part of that compilation time hit.

So I avoid it like the plague :) Seems like a massive negative hit to take on a project for something as simple as getting a map and a linked list, which take like a day to write, if that.

- Casey

Edited by Casey Muratori on