Author Topic: GC Alloc each frame  (Read 16947 times)

Marckeeling

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 8
    • View Profile
GC Alloc each frame
« on: February 27, 2014, 05:32:19 PM »
Ive been profiling our game after we were getting some GC.Collect calls thrashing the FPS. We've narrowed it down to two NGUI related calls. UIPanel.LateUpdate and the OnMouse events im guessing it creates. Ive attached a picture of the profiler.

All our UI does each frame is update a FilledSprites fill. It can be seen on the 3 green bars and the thermometer. That is ALL that we have updating here but eventually this seems to make the GC collect causing an FPS drop for us.

Ive tried moving the none static Sprites to their own panel to reduce having to redraw the entire UI but the allocation eventually comes.

Could I be doing something wrong or are these potential leaks here?

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: GC Alloc each frame
« Reply #1 on: February 27, 2014, 05:36:09 PM »
That 56 bytes allocation only happens inside Unity. If you look closely, that entire function's content is inside #if UNITY_EDITOR.

NGUI also doesn't use OnMouse events. That's a Unity event, so if you are using it in one of your functions, remove it and replace it with OnHover (bool isOver), which is an NGUI event.

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #2 on: February 28, 2014, 04:27:10 AM »
Hi, my reg email arrived so i'll continue this.

This still happens when profiling on Vita.

The deep profile (on PC) shows its reallocating for the mesh which is linked to my UI panel. Im not adding anything to this panel. All im doing is moving the fill amounts for 2 sprites and enabling/disabling something floating GUI over the player to show hes in danger.

See attachments for the profiler

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #3 on: February 28, 2014, 05:41:35 AM »
I identified the issue was with enabling/disabling a sprite. I tried just fading the alpha to see if that would stop a re-draw but no luck.

What are the common things that cause a re-draw? I need 3 or so icons to appear at specific events:

Notification icon when near an interactable object
Exit indicator (displayed when over X distance away from exit)
Damage indicator (displayed when taking damage)

These are world space sprites so they do require me moving their position a lot too. Is it the position change causing the redraw or the enabling?

dillrye

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 1
  • Posts: 30
    • View Profile
Re: GC Alloc each frame
« Reply #4 on: February 28, 2014, 02:43:43 PM »
Im pretty sure you setting a value on the sprite every frame is causing it to be seen as "dirty" and its rebuilding the vertex buffers.  That being said, the buffers themselves are reused and shouldn't be causing any memory allocation. I would try to only set values on it when changed and see what happens then.  Also, make sure you are upgraded to the latest version, there are some vertex buffer size fixes.

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #5 on: March 03, 2014, 03:10:14 AM »
Theres definitely an issue here. Ive moved the culprit into its own panel so it should be a draw call of one Sprite. Same thing happens (see attached)

Here is the code I use to update that Sprite:

  1. private void UpdateExitSymbol()
  2.     {
  3.         if (Vector3.Distance(Blaze.transform.position, new Vector3(512, 0, -512)) > 20f)
  4.         {
  5.             Vector3 screenPos = Camera.main.WorldToScreenPoint(new Vector3(512, 8, -512));
  6.             float fBounds = 17.5f;
  7.  
  8.             // Move the sprite in screen space
  9.             screenPos.x = Mathf.Clamp(screenPos.x, fBounds, Screen.width - fBounds);
  10.             screenPos.y = Mathf.Clamp(screenPos.y, fBounds, Screen.height - fBounds);
  11.  
  12.             Vector2 worldPos = Level.Instance.GetUICamera().ScreenToWorldPoint(screenPos);
  13.             FireExitSymbol.transform.position = worldPos;
  14.  
  15.             NGUITools.SetActive(FireExitSymbol.gameObject, true);
  16.         }
  17.         else
  18.         {
  19.             NGUITools.SetActive(FireExitSymbol.gameObject, false);
  20.         }
  21.     }

The GC alloc happens constantly when the active state changes and only frequently when I move the Sprite. Is this a bug or am I doing something seriously wrong here?

doggan

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 13
    • View Profile
Re: GC Alloc each frame
« Reply #6 on: March 03, 2014, 04:13:58 AM »
Not sure if this is related, but I've spent the past couple hours tracking down a GC spike in my application as well. If you have multiple panels active at the same time, a seemingly harmless change to a widget (that causes a re-draw) may cause the shared buffers of the two panels to fight with one another and cause alloc/GC issues: http://www.tasharen.com/forum/index.php?topic=8403.0

Nicki

  • Global Moderator
  • Hero Member
  • *****
  • Thank You
  • -Given: 33
  • -Receive: 141
  • Posts: 1,768
    • View Profile
Re: GC Alloc each frame
« Reply #7 on: March 03, 2014, 04:16:27 AM »
Strange.

I mean there are multiple potential causes: Vector3.Distance, worldToScreenPoint, ScreenToWorldPoint,setting position every update, NGUI's SetActive.

Try to turn them off one by one and see if the GC goes away.

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #8 on: March 03, 2014, 04:34:36 AM »
Thanks. In this case it was NGUITools.SetActive which was causing the alloc. The GetComponents inside that function was triggering it.

Setting the transform was also causing an alloc too. I removed the sprite transform set to a panel transform set and this issue is no longer present.

I am however having the same issue as doggan. With a filled sprite needing an update each frame is causing a re-draw which inturn causes a GC alloc to throw.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: GC Alloc each frame
« Reply #9 on: March 04, 2014, 12:58:15 AM »
Latest Pro version has per-draw call buffers, so this should address the allocation issues.

wom

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 34
    • View Profile
Re: GC Alloc each frame
« Reply #10 on: March 04, 2014, 02:18:03 AM »
Be aware of the 2nd page of this topic: http://forum.unity3d.com/threads/228630-Notes-on-Avoiding-Memory-Allocations/page2

Looks like the latest-ish version of Unity may have some kind of regression involving extraneous memory allocation for GetComponent() calls, especially if you're looking for a component that isn't there (which is a valid usecase).

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #11 on: March 04, 2014, 06:51:56 AM »
Latest Pro version has per-draw call buffers, so this should address the allocation issues.

Hey Aren, im still get GC alloc triggers in late update. Although less frequently now. Just on a panel that animates a FilledSprite.

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #12 on: March 04, 2014, 10:31:18 AM »
Hey Aren

After some hacking and twiddling ive come up with a solution to this. No idea if its perfect or what it'll break internally. In BetterList the trim methods were getting called when the list size shrunk which caused a re-alloc everytime.

I removed this call to make it retain the max memory it has allocated thus no re-allocation is needed. I also increased the initial allocation on better list to 64 and the max check now clamps to 128. Might be overkill.

Like I say I dont know if this trimming causes issue elsewhere but this has completely removed and GC allocations NGUI was causing in re-building.

Here is what I changed inside BetterList:

  1.         void AllocateMore ()
  2.         {
  3.                 T[] newList = (buffer != null) ? new T[Mathf.Max(buffer.Length << 1, 128)] : new T[64];
  4.                 if (buffer != null && size > 0) buffer.CopyTo(newList, 0);
  5.                 buffer = newList;
  6.         }
  7.  
  8.         /// <summary>
  9.         /// Trim the unnecessary memory, resizing the buffer to be of 'Length' size.
  10.         /// Call this function only if you are sure that the buffer won't need to resize anytime soon.
  11.         /// </summary>
  12.  
  13.         void Trim ()
  14.         {
  15.         //if (size > 0)
  16.         //{
  17.         //    //if (size < buffer.Length)
  18.         //    //{
  19.         //    //    T[] newList = new T[size];
  20.         //    //    for (int i = 0; i < size; ++i) newList[i] = buffer[i];
  21.         //    //    buffer = newList;
  22.         //    //}
  23.         //}
  24.         //else buffer = null;
  25.         }

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: GC Alloc each frame
« Reply #13 on: March 05, 2014, 08:01:15 AM »
Yea, don't do that. Trim() is necessary so that you don't waste memory. Trim() only gets called when the draw buffer size shrinks.

GavinWoods

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: GC Alloc each frame
« Reply #14 on: March 05, 2014, 08:26:38 AM »
This seems to solve all problems with GC allocations. Now the buffer only needs to grow to accommodate new items thus reducing the need to re-alloc when an items is enabled/disabled. Simply hiding a sprite for a few frames caused Trim to re-allocate and when it became visible caused it to re-allocate a higher amount.

This way it constantly has the max size it needs. Ive monitored the memory usage and it stays constant without any GC allocations.