I’m not sure what the deal is, I’ve been using Addressables for about a year or so now and everything seemed pretty fine with them. However, recently players have been reporting some lag spikes. After digging into it I’ve found that it is related to instantiating objects via InstantiateAsync(). I’ve been using it for a while, so I’m not sure why it suddenly noticeable, although maybe I just didn’t personally know it because my test computer is faster than most players. I’ve noticed this occurs in 3 other spots in my game when using the calls as well, and I’m assuming more, I just haven’t tested them all.
So I create around 8 explosions at once using InstantiateAync(). Then the lag spike occurs. I can see it in the Profiler and it looks in part due to a huge amount of calls for the garbage collector. Over 23,000 as can be seen in the screenshot!
Does anyone have any insight here? I’m not sure if a new version of the Addressables is to blame, or if I’m just now noticing it. I spent a good chunk of the day trying to figure it out, but haven’t. I’ll continue taking a loot into it tomorrow, but I figured I’d see what anyone has to offer in the meantime.
Ok, so I’ve been screwing around with this and I think there is something wrong inside the Addressables implementation, or bad design, or a bug or something. I tested in my game with a very basic cube addressable, so to make things as simple as possible. Here are some of my findings:
-Changing the group that the addressable is packed in can GREATLY increase/decrease the amount of time and GC used. Certain groups seem to trigger GC.Alloc significantly more. Meaning one group has ALOT slower time than another.
-Bigger groups seems to cause more lag, aka GC.Alloc.
-Changing a group to “pack separately” will lower the GC.Alloc.
-Simply creating 10 basic cubes using InstantiateAsync() is unable to stay under 60fps. Which is definitely not acceptable.
-Regardless of the group, a ton of GC.Alloc calls are being made on even the “better” performing groups. Big groups can cause around 15mb of GC.Alloc, where a small group with nothing but the cube asset still trigger 0.5mb, which is still a lot.
-Changing the label of the asset doesn’t seem to have any effect.
-You have to test using the actual “Use Existing Build”. If you test with “Use Asset Database” the issues I mentioned will not happen. This is probably why it took me so long to notice.
-I downgraded to version 1.19.13 to see if there was a recent change that causes this. It did not fix the issue.
-To answer spiney199’s question, using LoadAssetAsync() and then instantiating the object using the traditional Instantiate() method works perfectly fine as it should. However, it was to my understanding that InstantiateAsync() was their recommended way of doing things, as it keeps track of all the reference counting and such for you.
So those are my finding. Unfortunately, I create a lot of things in my game ranging from enemies, bullets, explosions, items, visual effects, etc using InstantiateAsync, as I thought that’s what you were supposed to use. From my tests today, I am concluding that the performance of InstantiateAsync() is absolutely atrocious. I’m sure my game is suffering from quite a bit of performance degradation everywhere I create object’s using InstantiateAsync(), which is in a lot of places like I mentioned.
I may go ahead and file a bug report under this, as I don’t think this is anything on my end. At this point, I’m not sure if I should slowly migrate away from using InstantiateAsync() or what.
Honestly the devs here have mentioned a few times that loading into a local reference, releasing the handle, and then instantiating that reference is in many ways, easier to use.
Load reference into local field
Release the handle used to load (you still have the local reference)
Asset is de-referenced when the object referencing it is no more (such as scene unloaded)
I find this easier as you don’t have to keep track of the references, as you’re already releasing the one reference Addressables cares about in the process.
Yea, I’ll probably have to go ahead and do that. It just seems a bit weird that the InstantiateAsync would have such ridiculously bad performance. Especially, since the bigger an asset group is, the worse the performance seems to get. Those are so bad of numbers that the documentation should just warn to stay clear of it at that point.
Yes the addressables ref count goes to 0, but no it doesn’t unload the local reference. Once you have a hold of the loaded asset you can safely release the handle. Once whatever object that reference is in gets destroyed, it gets cleaned up in time as normal.