#TMPro Managed Memory Allocations

1 messages · Page 1 of 1 (latest)

timid kite
#

Hello!

Earlier I wrote a short script that just instantiates and disposes of a system in a loop for X times.
At the end of all loops GC.Collect() And Resources.UnloadUnusedAssets() are called.

The problem is really big TMPro allocations in managed memory. Specifically, some really big Dictionaries and collections.
At the end of the run, only 1 copy of each Unity Object exists in memory, and all managed memory I used inside the system itself is cleared.
From the profiler, each iteration in the loop also clearly unloads all addressable assets (as the system's Dispose() should).

I'm wondering if anyone here could point me in the right direction in how to not cause or at least mitigate this.
Thanks in advance!

wispy merlin
#

Sharing the code would be helpful too

timid kite
#

I stripped out some of the unimportant lines before and after calls to Addressables

wispy merlin
#

Where's the loop you were discussing?

timid kite
wispy merlin
#

So you're saying after you dispose everything this stuff remains in memory?

timid kite
#

Yup, the screenshots above are from the comparison between the pre-run snapshot and the post-run

#

Should also mention that this runs in a Single-loaded scene which only contains the UI and GameObject to contain the script

dawn scaffold
#

@timid kite

  1. Replace Task with UniTask or Awaitable
  2. Did you trace what references these objects, preventing their gc? (via the memory profiler)
  3. Do not use pastebin its fulled with crap. ⬇️
#

!code

next otterBOT
dawn scaffold
#

If things remain loaded via addressables then you are releasing them incorrectly. ReleaseInstance() is for addressable Instantiations, Release() is for everything else.

timid kite
timid kite
dawn scaffold
#

This may just be font information that is required for tmp to work then

#

Do you have duplicated tmp fonts? (many copies in many groups)

timid kite
dawn scaffold
#

Oh btw remember that tmp settings asset is in Resources so that causes duplication that we cannot solve well

#

(unless they fixed this)

timid kite
#

only one group that contains the fonts. They're shared between whatever needs them.
Fixed this based on the warning in the addressable reports, but nothing changed

dawn scaffold
#

If you can re send the "loop" that would be great, the pastebin page wont load

timid kite
#

sure thing, one sec

dawn scaffold
#

I really urge you to use Awaitable or UniTask (best option) to ensure exceptions are not missed

#

here you skip awaiting a task in a coroutine so exceptions may get logged at some point later in time (or when you stop play mode in editor)

#

But I see no reason for TMP to keep creating all this crud tbh so it must be a bug or some strange issue

timid kite
#

I tend to be skeptical of the "it's a bug with the engine" most of the time until it's proven otherwise

#

I've looked online and I couldn't find anything specific to this even if this doesn't wound like an insane problem to have

dawn scaffold
#

You may be able to debug this with a debugger if you enable generation for

#

Many of those references were due to this

timid kite
#

I might try that, but if it's some internal issue to TMP I doubt I could fix that

dawn scaffold
#

I only suggest it so you can try to investigate if it really is a bug or is something you can work around

lavish rapids
dawn scaffold
#

tmp is within unity ui now, the tmp package is no longer used

lavish rapids
#

Well, it doesn't seem like they use the unity built in according to the class name/namespace in the memory profiler.

dawn scaffold
#

Its where i looked above in a 6.2 project

lavish rapids
#

In your screenshot it's using a different namespace

dawn scaffold
#

thats the assembly

#

oh yes the namespace is there too

#

I just looked for the ResourceManager and opened it

lavish rapids
#

And the class too

#

Their memory footprint seems to originate in the font asset.

#

Ah, I see a resource manager too.

#

Either way, it seems like they're using the old package, so perhaps changing that would fix it.

timid kite
#

and my Unity UI is up to date

dawn scaffold
#

yea like I said... its in Unity UI

#

I didnt lie i went and found the class just earlier :/

lavish rapids
#

I'm not doubting what you're saying. I'm saying that in the ts case it seems like the api is different.

#

Or it's just a memory profiler quirk.

timid kite
#

The only TMP (TMP_Text) reference I keep is in one of the GOs that gets unloaded in each iteration

lavish rapids
#

Actually, nvm. I guess I was looking at the assembly name after all.

#

It seems to be a cache of some sorts to speed up tmpro. Though, I don't think it would keep on growing. Probably would flat out at some point.

timid kite
#

Considering it's on a static class, maybe it's not the best to call when just a single system gets unloaded(?)

lavish rapids
#

It's not a great idea.

dawn scaffold
#

I wonder if you loaded the font asset first and never released it what would happen

lavish rapids
#

Assuming it flats oout, it's just consider it engine overhead and ignore.

timid kite
#

it's not small

lavish rapids
#

Well, 500 mb is not too much for fonts(shaders, textures, etc...) . Can you confirm that it keeps on growing after each iteration?

dawn scaffold
#

This was testing just this right?

timid kite
#

it does grow with each iteration for sure. Check the counter on the left.
Between pre-run and post-run count increases by 2k or 1k (depending on what object it is - 1k iterations)

timid kite
lavish rapids
#

I see.
Did you try what Rob suggested yet?

timid kite
lavish rapids
#

You'd need to look what references these assets.
I think tmp would do a lot of stuff under the hood if you just load a single tmp component. Like loading default or fallback font assets and everything that they depend on.

timid kite
#

The sprite object itself is not referenced by anything

#

Actually, the sprite is marked as unused.

#

The sprite object is a children of the unloaded handle

timid kite
#

Awaited the Resource.UnloadUnusedAssets()
Now the Unity object don't show up in the list anymore. The allocations are still there in the Managed that however.

timid kite
#

confirmed that TMP_ResourceManager.ClearFontAssetGlyphCache(); does nothing

lavish rapids
#

At this point I'd do what Rob suggested a while ago. Attach the debugger and step through the code to see where these are allocated from and if there is a mechanism of clearing these static dicts.

timid kite
#

From rob's suggestion I found out that TMP_ResourceManager.ClearFontAssetGlyphCache(); just rebuilds the cache instead of "clearing" it like the name suggests.

In addition, the only way in which a font asset is removed from the static collection is from a method which is not referenced by anything.
I'm guessing a possible fix would be to manually call this during the unloading of the whole object.

dawn scaffold
#

I saw it used the asset instance id

timid kite
dawn scaffold
timid kite
#

Still trying to figure this out
don't take this as 100% correct, I might be wrong

timid kite
#

Doesn't fix the issue if called on the font asset instances in the object NOR if called on a pre-loaded font instance

dawn scaffold
#

Try to identify why this data grows so you can make a bug report

timid kite
#

From what I could see, FontAsset contains a list of FontFeatureTable which should and the Glyph one.
From the breakpoints, the Clears do get called - but the final snapshot still shows the Glyph dictionary bloating anyway

chrome adder
timid kite
chrome adder
#

yeah, the line where your code calls a unity or package API that causes the allocations.

timid kite
chrome adder
#

yes, but have you identified the exact point of allocation.

timid kite
#

talking about my code, yeah
I pre-load the text once, and then it's loaded once with every addressables.loadassetasync since it's a component on that object

chrome adder
#

fwiw Resources.UnloadUnusedAssets(); is an async operation and you aren't awaiting it

timid kite
#

yeah I noticed that before
since then the only change (that actually did something) i made to the test is just awaiting that and the GC

#

guess I should have updated the snippet, my bad

chrome adder
#

np, i was more curious about reproducing the allocation stack trace in my project locally

#

from your code i can't really figure out what allocates, since you seem to be calling other systems of your own making.

timid kite
#

the only times I actually touch TMP are when I instantiate the GO and when I (try to) preload the font.
And post-run there are no left-over objects in Unity or managed memory coming from my own scripts. That's what's confusing me

lavish rapids
timid kite
#
         loadingA.Result.ClearFontAssetData();
            TMP_ResourceManager.RemoveFontAsset(loadingA.Result);
            TMP_ResourceManager.ClearFontAssetGlyphCache();
            Destroy(loadingA.Result);  // Added this line

            Addressables.Release(loadingA.Result);

            AsyncOperation unload = Resources.UnloadUnusedAssets();
            while (!unload.isDone)
            {
                yield return null;
            }
            unload = null;

This for example, reduces the amount it leaks by around 300MB

#

I tried building twice (without .Destroy) with the same code and once the leak appeared and the other time it did not.

dawn scaffold
#

just use async only so you dont need that nasty yield loop

#

(UniTask should let you await most things)

#

btw are you testing and profiling in editor too?

timid kite
timid kite
dawn scaffold
#

you can do that in editor too and is what i would do to let me use a debugger with ease

#

I know it includes other crap but id expect this specific thing to act the same in editor too

lavish rapids
timid kite
#

Nevermind, actually finally found a complete fix.
Infinite thanks to @dawn scaffold for suggesting that pre-loading fonts thing.

#

Apparently Addressables doesn't unload them from the internal cache, even if the bundle itself is gone.
You have to manually pre-load everything and call the 3 different methods.

Each time I instantiated the object without having loaded the fonts separately in a central spot just added them back to the cache. .Release() is not enough.
I couldn't find anywhere in the docs that

            loadingA.Result.ClearFontAssetData();
            TMP_ResourceManager.RemoveFontAsset(loadingA.Result);
            TMP_ResourceManager.ClearFontAssetGlyphCache();

are required on every font, otherwise Addressables is just going to leak for each object instance.

#

Unless I missed something in both the TMP and Addressables docs, this is a pretty big thing to just not say clearly in there in my opinion

dawn scaffold
#

Seems like a big flaw in how the resource manager works to me

#

asset bundles have soo many stupid problems and yet addressables only works thanks to them :/

timid kite
dawn scaffold
#

Its dumb yes but to me this is a bug because you did not load the font, addressables did

#

therefore it fucked up when you released the asset you loaded and addressables unloaded the font too

timid kite
#

I'm totally of the same idea
but that's what you gotta do.
Just glad I can finally move on

dawn scaffold
#

seeing as you half made a repo already plz report it as a bug

#

the inability to cross reference from build/resources to asset bundles is a massive flaw that still causes problems
Its soo easy to duplicate fonts both sides its mad

timid kite
#

I can do that for sure

#

thanks again for the help!

dawn scaffold
#

glad to have helped

zealous grove
#

There isn't any elaboration but heads up @dawn scaffold @timid kite new roadmap specifically mentions they've filled some sort of gap when it comes to textmeshpro x addressables, hopefully covers this kinda shit?

dawn scaffold
#

really? wow i need to watch that then
The ideal solution would be improve asset bundles but perhaps thats too much work

zealous grove
#

its literally a single offhand comment but it made me recall lurking this thread aha