If you want to reach competitive performance with other state of the art ECS libraries, I think you will need to rethink your whole storage architecture. Here are the things I would keep in mind when working on an ECS.
Data Locality
The speed of ECS is based on data locality. This means that data which is accessed in a query should be next to each other. Then, L1/L2 caches can be optimally used and memory access is very fast.
In Haskell terms, this means you may not store datatypes in a regular fashion, since they are allocated on the heap in a non-linear manner. Tree-based storage containers like IntMap
accelerate this problem. I think it is likely that your current storage spreads the stored items over the whole heap.
The simplest way to fix this is to allocate a big memory buffer and store elements via a Storable
instance next to each other. Of course, there are caveats: You need to handle low-level code, not everything can be made Storable
, arrays do not have infinite size, …
Optimize the query loops
You need to make sure to optimize the looping over all elements in a query since they are the hot path. You must not allocate anything within the loop and all storage functions should be inlined.
Let’s see this in an example:
for [1..elementsAmount] $ \i -> do
element <- getElement i
element' <- doStuff element
putElement i element'
In the above code, getElement
and putElement
are ideally inlined so that accessing the data is fast. Additionally, for [1..elementsAmount]
will most likely result in a simple for loop without allocating any indices, though here some benchmarking might be needed to verify GHC code generation works as expected.
In contrast, your storage DynamicStorage
is based on a record of functions. While this is very nice for composability, those functions are probably not inlined and will be slower as inlined versions as a result. Keep in mind, each call to a non-inlined function is an indirection. Additionally, entitiesDyn'
might just allocate all indices, tanking the performance.
Performance and Composability are often opposite goals and you will have to find a path in-between.