A thing to be aware of when doing micro-benchmarks is what you are testing.
In case of "bunnymark", it is "how many moving objects with little to no logic can I have", which, assuming that each respective engine has the general competency of using GPU for rendering and batching same-textured objects together using vertex buffers, effectively translates to "how little overhead there is to an object that has a sprite and nothing else".
The results are consistent with expectations:
- GameMaker historically has a certain amount of "luggage" attached to instances, which has them check for velocity, check if they have timers to tick, tick animation frames, etc.
Consequently, this also means that storing your buns in an array of structs or a ds_grid will yield much better results.
- Godot has a different kind of luggage - although objects don't have velocity and timers to calculate by default, they do have support for child objects and consequently calculate transformation matrices.
- Construct has little luggage - unless an object actively presents itself on the scene, it won't even have position variables. Although some of this advantage would fade as you add more complex logic to the objects, this makes it a good fit for this kind of comparisons, which had been the case for quite a while.
- You bring your own luggage for RayLib - with no entity system to boot, you likely wrote the exact amount of code to stuff your bun-structs into a vector and move them around, or maybe opted to use separate arrays per-property for better caching, or even used par_unseq to allow bun movements to be processed in parallel on multiple CPU cores, or even implemented GPU instancing... rest assured, the result unbeatable because the overhead comes solely from your own oversight.
In the end, what Ashley said - use the profiler to figure out pain points, and especially be wary of incorporating "cool tricks" that appear to perform better on synthetic tests.