Deceptive Performance sinks?

1 favourites
  • 6 posts
From the Asset Store
Firebase: Analytics, Dynamic Links, Remote Config, Performance, Crashlytics on Android, iOS & Web Browser
  • Does anyone know any good practices that will help keep performance good?

    Or little things to avoid, that might be deceptively simple yet have a big cost?

    Sometimes things can seem fine in a project, but then as the project becomes bigger over time, all the little inefficiencies start to add up!

    For example, I saw someone having performance issues on Discord, and all they were doing is using the action "Move to Object" every tick!

  • Ooh I love this kind of thing!

    I try to keep a mental note of what is considered an "expensive" event, and what is not - There's no list that I know of, but I mostly memorised a lot of it over the many years. Some expensive events being: Changing an object's Z order, collisions (especially if it has a complicated collision polygon, but still expensive even if it is a 4-point box), creating many objects in 1 tick, calling functions many times (especially if the function has an objects UID sent across and there are many instances of that object - Perhaps the new Custom Actions helps with lowering the need for C3 to check every object to find the correct UID).

    If something IS expensive but required, and is running every tick, you can try to think "OK, when do I actually need these events to run?". In some cases, it is required to run some events every tick, but sometimes you only need to run events occasionally, E.G. Only when a player's position changes - In this case, you could track the LastX and LastY of the player, and then every tick, check if self.LastX is NOT equal to self.X (same with LastY in an "Or" block), and then throw your events into here as sub-events, then at the very end of these sub-events, set LastX and LastY to self.X and self.Y (Note: This has the added benefit of allowing you to check distances travelled in pixels since the last tick). So overall, this creates another check every tick where it checks the players position compared to LastX and LastY, but this could significantly save on performance if the sub-events within this are quite expensive. I may not have chosen the best example as you may only have 1 or 2 player instances that are usually always moving around, making this slightly more expensive overall, but the same concept could be applied to anything that has multiple instances existing and each instance could be either moving or stationary, such as many interactive items scattered around the map and such.

    With collisions, I find this can vary significantly - Sometimes it is best to filter the object picking first, and try to keep the collision event at the very end, so that less collision checks are occurring. But, sometimes utitilising the collision cells in C3 works well, so it can sometimes be more performant to keep collisions at the very top of an event block - Definitely worth experimenting and measuring CPU performance each time to see what is best for your situation.

    With effects, particularly 1-frame objects with a non-animated effect (such as AdjustHSL on a scenery object) if you do not often need to adjust the effect parameters, then consider using the drawing canvas - You can paste the object with its effects, then hide/disable the original object's effects, which helps with GPU performance. If you must use a sprite for this object, you could implement a system to load the drawing canvas result back into the sprite via "Load image from URL", which if you make this system, you could do this for each animation frame. Again though, this is only for non-animated effects such as AdjustHSL, and not for effects such as warping water or noise effects.

    With effects on objects or layers, try to disable them when not needed, even if you have AdjustHSL or Contrast and they are at their default values that show no visual difference - At the end of the day, it is still generating the effect, so disabling and enabling effects when needed is key.

    Similar to the above, but for behaviours - If objects are offscreen or invisible, their behaviours simply don't need to be active when the player cannot see them or their behaviour is not important to anything else that the player can see, although be careful and always test thoroughly (E.G. You may not want physics disabled when offscreen, as this could cause issues with other physics objects colliding with it, if any. Thankfully, physics objects "sleep" when they don't move for a while, but I perceive this as "they are asleep, but they're actively awaiting an unexpected object to smash into them, so it must be doing some form of collision check even when sleeping").

    If you must spawn multiple objects quickly, try to spread this out across multiple ticks if it won't cause issues for what you're trying to achieve. If you need to spawn objects onscreen that the player will instantly see, then it is not recommended to do this as this could be visually jarring, but sometimes it would not have any visual impact at all (E.G. If you had 10 objects explode and 3 particle objects are spawned for a fire & smoke effect, you could wait a tick for each of the 3 particle effects to spawn in, so that you only have 10 objects spawning in 1 tick, rather than 30 spawning in 1 tick - a fire effect sort of gently appears and fades in, so this would not look visually jarring if the smoke particle spawns in a tick later). Again, this is very situational, but always worth considering. Anything for dem sweet performance gains.

    Further to the above, you could create the objects earlier and have a "pool" of objects to pick from, so that you are not creating many objects per tick. If you can determine a minimum amount of objects that would ever need to appear at once, and it's not over like 50 objects (50 is a random number, could be lower on mobile), then you could create these at start of layout and keep them invisible, make sure everything about the object is disabled (no behaviours enabled, particles not spraying), and have a Boolean value in the object called "IsActive", then upon needing one of these objects, you can simply do an event for "IsActive = False" and "pick nth instance 0" to get an object, and then set IsActive to true whilst it is in use (and set it back to false and hide it again when no longer needed - don't "hide" the object every tick, do it in the event where the object is becoming inactive).

    With particles, if you are a heavy user of particles, consider dynamically adjusting the rate/lifetime whenever many particles exist (rate is probably more appropriate, since lifetime could visually ruin an effect, but still worth considering). E.G. If you had to spawn in 10 confetti particles effects, then upon creating these, check how many other active particles exist and lower the particle rate, and also pick other existing particles to lower their rate too. It is best to determine the lowest rate that looks visually appealing, so that you don't set the rate too low and ruin the effect. If there's many particle objects, it may be expensive to have to loop through all objects to lower their rate, so be careful and mindful about this, and always measure performance.

    Try to not have too many layers with "force own texture". I always thought many types of effects required "force own texture", but it's surprisingly few and far between - In my effect-heavy game, out of about 30 layers, I only need 2 layers with "Force own texture" enabled. It is mostly needed when using blend modes such as "destination out", "source in", etc, and whenever you want to blend two additive objects without taking into account the layers below. If you require blend modes such as destination/source, then it may be worth exploring the drawing canvas plugin, as you can paste objects with blends into the drawing canvas and it can "carve" shapes out of the drawing canvas and such. Pasting objects can be more expensive overall, but if its between using "force own texture" on a layer, compared to pasting 2 objects on a 100x100 drawing canvas, then it might be worth going with the drawing canvas (again though, measure CPU/GPU performance to see if it was truly worth it!)

    When using the Preview Debugger to watch event CPU usage, you may find that a specific group is reaching high CPU usage. If you are unsure what could be causing it, you can create many small groups in this group, name them A, B, C, D, etc., and then put only 1 event in each of these groups (don't accidently reorder your events!). Then upon checking the debugger again, you are likely to discover which specific event was causing the high CPU usage, and you can figure out a solution from here.

    It is worth having a SpriteFont always onscreen with a few variables displayed, such as FPS, CPU utilisation, GPU utilisation, and Object Count. Recently, I found that my large project was lagging after long play sessions, but I realised the "object count" was increasing way higher than it should, which gave me the clue that I was not destroying objects correctly - After realising this, I could then use the debugger, play the game for a while, check the list of all object counts in the debugger, and discovered which specific object was not getting destroyed and resolved the lag issue.

    That's a few thoughts off the top of my mind, hope someone finds any of this useful. I'm interested to learn more and am curious what anyone else will post.

  • I think collision checks now are pretty well optimized and not as bad as they used to be. You shouldn't worry too much about running a few hundreds or even thousands overlapping/collisions checks on every tick or making complex polygons where needed.

    Creating many objects on every tick is also not that bad by itself. It's loading textures what's usually causing lags. So if the images are already in memory and the objects don't have many behaviors, spawning them shouldn't use a lot of resources. In my project I'm periodically creating and destroying thousands of small objects, and most times the lag is not noticeable.

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • I think collision checks now are pretty well optimized and not as bad as they used to be. You shouldn't worry too much about running a few hundreds or even thousands overlapping/collisions checks on every tick or making complex polygons where needed.

    Creating many objects on every tick is also not that bad by itself. It's loading textures what's usually causing lags. So if the images are already in memory and the objects don't have many behaviors, spawning them shouldn't use a lot of resources. In my project I'm periodically creating and destroying thousands of small objects, and most times the lag is not noticeable.

    Ahh that's good to know, thanks! Admittedly I'm a bit out of the loop with collision stuff due to working on a rhythm game that pretty much never relies on collision detection, but it's good to know as I assumed to avoid complex polygon shapes whenever I did return to this.

    With creating objects, perhaps in my case, I have some objects that, when created, run an function to initialise the object, which does a whole heap of stuff to them to get the objects prepared (creating further objects to attach to the original object, setting a bunch of variables, etc.). Sometimes many of these objects get created at once and creates a jolt of lag. That might explain why I've needed to explore different ways of approaching the creation of objects, but now I'm wondering about optimising the initialise function... Hmm...

  • Funny enough, I've many times encountered the issue of "events/actions running that aren't supposed to run, but without noticable bugs"

    I usually notice it when I add sound to things and the sounds go haywire unexpectedly. The event/actions in it do not cause any other issues, resulting in an event being run every tick for no purpose. I'd assume that's kind of an invisible performance sink, especially if it happens with multiple eventblocks and per object.

    I also notice it sometimes when putting browser.logs somewhere and then see it being triggered constantly when it shouldn't, but your headphones blowing up with 240 sounds/second is much more prominent. :)

  • My best advice is: make measurements and be scientific about it. I regularly see weird claims and things that sound technically impossible. People often do things like make multiple changes and then misattribute the thing that actually helped, or make mistakes and misattribute the thing that is making it worse. Software systems are complex, results are not always what you'd think, and even experienced software engineers lose track of the things they've changed along the way.

    If you think something is causing a performance problem, you should be able to measure it, make a single specific change, and then measure a significant improvement afterwards. If you're not doing that, it's not much better than rumour.

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)