Author Topic: Terrible performance with NGUITools.AddChild  (Read 6524 times)

boudinov

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Terrible performance with NGUITools.AddChild
« on: April 17, 2014, 11:22:37 AM »
Hello,
We are currently evaluating NGUI, in order to replace our old UI system. On several locations in the UI, we have scroll views with grids, which can contain dozens of child items - like player inventory items, shop, etc.. These items can be prefabs, made of several labels, progress bars, sprite(s).

When we add 30 of these items, using NGUITools.AddChild, there is almost 200ms spike in the Profiler.
After adding them, when just deactivate/activate the scroll view, the spike is like 50-60ms, which is also not quite acceptable.
It is a powerful Itel I7 PC, the tests are being made on.

These spikes are alike even when attaching the items to an empty scene UIRoot directly - no scroll view/grid at all.

When instantiating a simple prefab with just 1 sprite, the UICamera.Update+UIRect.Start spike is around 15ms. The complex prefab has 7 labels, 1 background sprite, 1 texture, 4 progress  bars in it, and Math proves the problem does not seem to be in the complex prefab.


Am i doing something wrong, and if not, what is the strategy for dealing with such scenarios - paging or lazy instantiate of some sort?
NGUI version 3.5.3 btw.

Thank you!
« Last Edit: April 17, 2014, 11:47:42 AM by boudinov »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #1 on: April 17, 2014, 01:18:26 PM »
Look inside that function. All it does is calls Instantiate() and resets some parameters. If you replace it with just Instantiate() you will get the same effect. Look at the profile, 101 out of 110 ms is spent in the Instantiate() call itself, which is Unity. Instantiating things is expensive in Unity. Instantiating or even just activating UI elements also means having to re-create the draw buffer at the end of the frame, which is also expensive.

boudinov

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #2 on: April 17, 2014, 06:51:23 PM »
Thanks for the answer Michael!

So we need to handle these two situations without getting severe performance spikes:
1) Populate grids with lots of items
2) Deactivate/Activate grids with lots of items

For 1) Several solutions may be possible:

- Pre-Instantiation and Pooling of items. When pre-instantiating, should the items be left active 1 frame, so they are 'pre-warmed' (UIRects.Start handlers are called upfront), and activating them later will be cheaper?

- Init Grid with lightweight placeholder items (get them also from pool maybe), and Instantiate/GetFromPool real item, when placeholder gets visible. Is this even possible, how hard will it be to decide that a placeholder needs to show the real item?

- Instantiate items prolonged in time, one by one. Spike will be avoided, but ScrollView will have to rebuild geometry each time a new item is added.  Is this feasible?

For 2) Seems the great spike may be when we activate a heavy UI sub-hierarchy for the first time. So a sound solution would be to pre-activate all heavy UI herarchies at scene load and deactivate them right after - shall that help?

Which of these sound true?
Thanks!
« Last Edit: April 17, 2014, 07:36:57 PM by boudinov »

wallabie

  • Full Member
  • ***
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 200
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #3 on: April 18, 2014, 04:17:42 AM »
I would like to point this example out as a good solid use case for having a pooling system support inside of NGUI.  Like Daikon but since it's NGUI, probably would be a better pooling system.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #4 on: April 18, 2014, 04:31:16 PM »
I would say pre-instantiate the items, place them in a local pool of instantiated prefabs (just make a List<GameObject>), and pull items from that pool, updating their data and then enabling them as necessary. Assuming all your instantiation happens in the same class (no reason it shouldn't), adding a list of game objects to it would be trivial.

boudinov

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #5 on: April 21, 2014, 05:29:52 PM »
Hi,
So pre-instantiating solves half of the problem.
The other performance problem is activating the scroll view with 30 items in it, especially for the first time.

When pre-instantiating, if we let the items be active for 1 frame, then deactivate, consequent scroll view activation times gets much better:
* UIPanel.LateUpdate takes much less time
* UIRect.Start is not called any more
* UICamera.Update (the button click handler that activates the ScrollView) however starts taking much time to execute (calling UIRect.OnEnable internally), but is still better than UIRect.Start

Is this a normal thing to do to further avoid performance spikes?

As seen in the uploaded sceenshot NGuiActivate.png, UICamera.Update() takes quite some time 45ms, and this is the consequent ScrollView activation, which is actually the better performant.
I understand draw buffer is re-created in UIPanel.LateUpdate, but what happens in UICamera.Update(), and can we avoid it somehow?
P.S. It seems, draw buffer is invalidated and widgets get reattached to the first found parent Panel. Can we manually initiate that when needed, and let it not be automatically done on gameobject activation?

Thank you!
« Last Edit: April 22, 2014, 04:45:13 PM by boudinov »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #6 on: April 22, 2014, 04:44:43 AM »
UIPanel.Update doesn't exist. Did you mean UIRect.Update? If so, that's where the anchors get updated.

P.S. If you don't want to wait a frame, you can also force an immediate rebuild by calling UIPanel.Refresh(). So you can enable your UI, call Refresh on the panel, then disable it, all without skipping a frame.

boudinov

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 10
    • View Profile
Sorry, i meant UICamera.Update (fixed previous message), with a callstack like this:
UICamera.Update
--UIToggle.OnClick
----GameObject.Activate
------UIRect.OnEnable

This is the thing that takes most time when activating a populated scroll view. UIPanel.LateUpdate takes much less time.
In UIRect.OnEnable its seems, widgets get almost entirely re-initialized. What is the advice to keep this overhead to a minimum?

UIPanel.Refresh i don't see a good place to call it, before deactivating some UI hierarchies, because Widgets Start handlers should run first. This calls for some wicked custom script execution order and coroutines - which will not work naturally with UIToggledObjects for example.
So i added a property 'OnNextFrame' to UIToggledObjects script, and skipping the first frame seems more straight forward overall.
« Last Edit: April 22, 2014, 04:52:05 PM by boudinov »

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Terrible performance with NGUITools.AddChild
« Reply #8 on: April 23, 2014, 05:27:10 AM »
Based on that call stack I just see you clicking on some toggle that activates a remove object, which causes UIRects to get enabled. The UIRect.OnEnable doesn't do much inside. It merely calls the OnInit() function, but only if it has already been started. Otherwise it pretty much simply exits the first time around. Just check the code.

It also calls OnValidate, but that only happens in the editor. So if this is what's showing up, then you need not worry -- it's an Editor-only thing.