Enhancing the performance of games: a few ideas #5189
Replies: 1 comment 2 replies
-
Thanks for opening this discussion! A few things:
Probably using a Map is a good idea yes, at least internally.
I think that's a good idea overall.
Unsure if this is not a micro optimization, I would rather do this after improving the rest.
As you said, I think there is optimization to be first done to even avoid copying list ("copy on write" optimizations, or this kind of stuff during code generation). For example, we already do something (search I'm tagging @D8H because he recently simplified a bit the code generation (removing global booleans in favor of more readable
This seems like a very nice library. From what I remember when I did a test the important points are:
I think though that we would already gain 80% of performance by having the collision methods (+ raycast + distance conditions) uses objects that would be hold in a spatial structure, R tree or even just a "dumb" grid (as the cost to update it is super super small). |
Beta Was this translation helpful? Give feedback.
-
Migrating away from Hashtable
Hashtable
missuses an object as a map, whereasMap
is more adapted, standard, properly typed, and most importantly performant. We should work towards replacing instances ofHashtable
withMap
, then ultimately fully remove it.Refactor to remove gdjs.Variable
gdjs.Variable is most likely a performance hog. It allocates one variable for each javascript type per variable, is a full class with prototype etc. It must be very hard for javascript engines to optimize, as they cannot infer the shape of the data and generate performant data structures from them (e.g. it cannot optimize an array of numbers).
Instead of a class, we could use real javascript variables directly, replacing the methods with functional helpers. We would also have a different expression type for the intent of reading and the intent of writing.
For example, if
then the expression
VariableString(GameState)
would generate :Variable(Player.HP)
would generategetAsNumber/getAsString/etc handle the case where variables.Player or Player.HP returns undefined. That way, we also ensure that expressions never create child variables if they do not exist too, which is a better behavior than creating empty variables.
The expression
Image.DataFromFS
used in a for loop event set to write the children toImageBits[Variable(Image.Name)][0]
would generate like this:The set variable to boolean
false
with the variablePlayer.Alive
would generate like this:This should offer much more context to v8 and allow it to optimize variables much better, and to reduce the memory footprint of GDevelop games that use a lot of variables. This would also make tasks like loading an image from the internet/file system as an array to then insert it into the game much more performant and practical.
Using Typed Arrays for common number properties for cache locality
Some properties like x and y, height and width are commonly used together. Additionally, operations are often done with multiple instances of the same objects. We can optimize math heavy operation that use these a lot (e.g. collisions, behavior's movement calculus) by putting this data in contiguous memory (data oriented design) via typed arrays. Example pseudo code:
Using integer lists when doing object picking
It was established once I think by 4ian that object picking is especially slow in GDevelop.
Ideally, we could try and avoid copying the lists of objects at every event level, but I keep on turning the problem around and around and I cannot think of a good data structure for slowly filtering out objects more efficiently.
I am not sure if this optimization is worth it, due to the overhead of having to fetch the object in an array afterwards. I think mutating a number array like this is more efficient than an object array, and that an object array used as a lookup table rather than being constantly mutated would be faster, but that's just a hunch and I may be very very wrong.
So instead of using another data structure, what about optimizing the list to be more efficient?
We can use typed arrays again with the GDevelop object IDs (
obj.id
). We know IDs are unsigned integers, so we can use an Uint32 array. This introduces a technical limit of objects created throughout the lifetime of the game to4,294,967,295
. If we want to be on the safe side we could use an Uint64 which is truly a fully unrealistic limit to reach (18,446,744,073,709,551,615
), but this would make us useBigInt
s, which are a hassle and less optimized...Since typed arrays are fixed size and we do not want to resize them all the time for obvious perf reasons, we would use the first index to store the "length". This ensures that object picking array memory is never deallocated which would be bad for perf and not a memory leak: after all, if the array grew to be very big at a moment, we know that the game has the potential of reaching that amount of objects and must keep the array at that huge size for when it happens again without having to resize a lot again.
To get the object by ID
Example generated pseudo-code:
Use Rust WASM code for collisions
Parry is a notorious efficient collision-detection library made by the creators of Rapier, a notorious rust physics engine. WASM being especially good at number crunching jobs, and parry (& rapier) being designed for being compiled to WASM (as is seemingly the trend in rust land currently, apparently?), it would likely be much more performant than our JS solutions. This one would be relatively easy to test out:
Beta Was this translation helpful? Give feedback.
All reactions