ECS? I'll actually suggest that it may not be premature if so to put a lot of thought into the data-oriented side of the design and benchmark different reps because it could impact your interface designs, and the latter is very costly to change late in the game. Also ECS just demands a lot of work and thought upfront and I think it's worth utilizing some of that time to make sure it's not going to give you design-level performance grief further down the line given how it's going to be at the heart of your entire freaking engine. This part glares out to me:
unordered_map<string,[yada]>
Even with small string optimizations, you have a variable-sized container (strings) inside another variable-sized container (unordered_maps). In fact, the small string optimizations could actually be as harmful as helpful in this case if your table is very sparse, since the small string optimization would imply that each unused index of the hash table will still use more memory for the SS optimization (sizeof(string)
would be much larger) to the point where the total memory overhead of your hash table might cost more than whatever you're storing into it, especially if it's a simple component like a position component, in addition to incurring more cache misses with the huge stride to get from one entry in the hash table to the next.
I'm assuming the string is some kind of key, like a component ID. If so, this already makes things dramatically cheaper:
unordered_map<int,[yada]>
... if you want the benefits of being able to have user-friendly names that scripters can use, e.g., then interned strings can give you the best of both worlds here.
That said, if you can map the string to a reasonably low range of densely-used indices, then you might just be able to do this:
vector<[yada]> // the index and key become one and the same
The reason I don't consider this premature is because, again, it could impact your interface designs. The point of DOD shouldn't be to try to come up with the most efficient data representations imaginable in one go IMO (that should generally be achieved iteratively as needed), but to think about them enough to design interfaces on top to work with that data that leave you enough breathing room to profile and optimize without cascading design changes.
As a naive example, a video processing software which couples all of its code against this:
// Abstract pixel that could be concretely represented by
// RGB, BGR, RGBA, BGRA, 1-bit channels, 8-bit channels,
// 16-bit channels, 32-bit channels, grayscale, monochrome,
// etc. pixels.
class IPixel
{
public:
virtual ~IPixel() {}
...
};
Isn't going to get far without a potentially epic rewrite, since the idea of abstracting at the single pixel level is already extremely inefficient (the vptr
itself would often cost more memory than the entire pixel) compared to abstracting at the image level (which will often represent millions of pixels). So put enough thought into your data representations in advance so that you don't have to face such a nightmare scenario, and ideally no more, but here I do think it's worth thinking about this stuff upfront since you don't want to build an intricate engine around your ECS and find that the ECS itself is the bottleneck in ways that require you to change things at the design level.
As for ECS cache misses, in my opinion developers often try too hard to make their ECS cache-friendly. It starts to yield too little bang for the buck to try to access all your components in a perfectly contiguous fashion, and will often imply copying and shuffling data all over the place. It's usually good enough to, say, just radix sort component indices prior to accessing them so that you are accessing them in a way where you at least aren't loading a memory region into a cache line, only to evict it, and then load it all over again in the same loop just to access a different part of the same cache line. And an ECS doesn't have to provide amazing efficiency all across the board. It's not like an input system benefits from that as much as a physics or rendering system, so I recommend aiming for "good" efficiency across the board and "excellent" just in the places where you really need it. That said, use of unordered_map
and string
here are easy enough to avoid.
the answer to this question is almost always a resounding "YES !!"
. I still feel that it is better to get it working first & optimize later, but YMMV, everyone has his opinion, all of which are valid, and only the OP can really answer his own - subjective - question.