Author Topic: Huge performance drop after updatating from 2.7.0 to 3.7.5  (Read 20731 times)

newlife

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 23
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #30 on: April 28, 2015, 07:03:20 AM »
I can finally reply after the release of the update of our app.
ArenMook, I think that you misunderstand the aim of the thread i opened here. You are trying everything to "demonstrate" that im wrong and that you product is cool.
There is no reason to do that, im using NGUI cause I think that its the best GUI for Unity.
The aim of this thread is to understand with you what is causing the performance drop, nothing else. It seems that you think Im writing here to degrade you product.
Do please reconsider that. A said, Im here to work with you in a win-win way, not to argue with you.
Let me know what you think.

Nicki

  • Global Moderator
  • Hero Member
  • *****
  • Thank You
  • -Given: 33
  • -Receive: 141
  • Posts: 1,768
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #31 on: April 29, 2015, 01:27:59 PM »
Well, there's a lot of things that changed with 3.0+. The structure of panels changed, which would potentially give you misleading information comparing 2.7 with 3+ with deep profiling. It may be, that these underlying changes are at root of the performance issue you've noticed. It's just really hard to pinpoint, since there's oceans of changes that's happened since then. That said, improving the performance is obviously something everyone wants.

I'll try to do the test you just did right now and measure some performance on the newest and see if there's anything glaring, if nothing else to confirm or counter your findings.

newlife

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 23
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #32 on: April 30, 2015, 03:56:18 PM »
Thank you for your reply Nicky. Keep in mind that i suggested to use deep profile just to have more information, the result is perfectly the same with normal profile in the editor and of course on a android device (and on any target i think, cause the performance drop is present in the editor).
Im curious to know the results of your tests.

Nicki

  • Global Moderator
  • Hero Member
  • *****
  • Thank You
  • -Given: 33
  • -Receive: 141
  • Posts: 1,768
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #33 on: May 03, 2015, 05:30:52 PM »
I have an in depth analysis of this written up on my other computer, but the forum has been down all day, so it'll have to wait until I'm home from work tomorrow.

The long and short of it, you can gain a lot of performance by switching the widgetsAreStatic on in the panel while dragging, and I did some optimizations to the existing code that should make it faster when dragging panels around.

Nicki

  • Global Moderator
  • Hero Member
  • *****
  • Thank You
  • -Given: 33
  • -Receive: 141
  • Posts: 1,768
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #34 on: May 04, 2015, 12:31:32 AM »
So, I've taken a closer look at this and I think mostly the performance changes you are observing is a consequence of how the system of NGUI has changed from 2.7 to 3+.

Analysis
I tested in the Editor 5.0.1p2 on the "Example 14 - Endless Scroll" by making about 200 entries in the middle green scroll list and while dragging or scrolling the UIPanel.LateUpdate balloons up to ~65% CPU usage as you also saw in your example, newlife. I'm testing without culling enabled on either UIPanel or UIWrapContent.


Using the deep profiler, we can dig a little deeper to see what could be causing this. Note, as always, that the numbers will be skewed as the deep profiler adds overhead to each method, which will also trigger a bit more garbage collection etc. That said, the picture looks very similar to the previous one.



6.5 ms to UIPanel.UpdateWidgets
4.8 ms to UIPanel.FillDrawCall

Looking deeper into UpdateWidgets we see this:



ApplyTransform seems to take the largest chunk of the work, UpdateGeometry itself second and UpdateGeometry and UpdateTransform about even. (Caveat: You see the List.Add and List.Clear instead of BetterList because I experimented with switching back to the regular list to see if there was a difference - there's not really.)

Let's take a look at the code for these methods:

  1. void UpdateWidgets()
  2. {
  3.         bool changed = false;
  4.         bool forceVisible = false;
  5.         bool clipped = hasCumulativeClipping;
  6.  
  7.         if (!cullWhileDragging)
  8.         {
  9.                 for (int i = 0; i < UIScrollView.list.size; ++i)
  10.                 {
  11.                         UIScrollView sv = UIScrollView.list[i];
  12.                         if (sv.panel == this && sv.isDragging) forceVisible = true;
  13.                 }
  14.         }
  15.  
  16.         if (mForced != forceVisible)
  17.         {
  18.                 mForced = forceVisible;
  19.                 mResized = true;
  20.         }
  21.  
  22.         // Update all widgets
  23.         for (int i = 0, imax = widgets.Count; i < imax; ++i)
  24.         {
  25.                 UIWidget w = widgets[i];
  26.  
  27.                 // If the widget is visible, update it
  28.                 if (w.panel == this && w.enabled)
  29.                 {
  30.                         int frame = Time.frameCount;
  31.  
  32.                         // First update the widget's transform
  33.                         if (w.UpdateTransform(frame) || mResized)
  34.                         {
  35.                                 // Only proceed to checking the widget's visibility if it actually moved
  36.                                 bool vis = forceVisible || (w.CalculateCumulativeAlpha(frame) > 0.001f);
  37.                                 w.UpdateVisibility(vis, forceVisible || ((clipped || w.hideIfOffScreen) ? IsVisible(w) : true));
  38.                         }
  39.                        
  40.                         // Update the widget's geometry if necessary
  41.                         if (w.UpdateGeometry(frame))
  42.                         {
  43.                                 changed = true;
  44.                                 //Debug.Log("Geometry changed: " + w.name + " " + frame, w);
  45.  
  46.                                 if (!mRebuild)
  47.                                 {
  48.                                         // Find an existing draw call, if possible
  49.                                         if (w.drawCall != null) w.drawCall.isDirty = true;
  50.                                         else FindDrawCall(w);
  51.                                 }
  52.                         }
  53.                 }
  54.         }
  55.  
  56.         // Inform the changed event listeners
  57.         if (changed && onGeometryUpdated != null) onGeometryUpdated();
  58.         mResized = false;
  59. }
  60.        

Foreach widget drawn it calls UpdateTransform and UpdateGeometry once, consistent with the deep profile. There seems to be no immediate "bad things"™ going on, unless there are side effects to some of the properties. There are  minor things we can do to improve it, like moving the int frame = Time.frameCount outside the loop (assuming overhead from Time.frameCount) and checking which of the (w.panel == this) or (w.enabled) is heavier, since if the first one fails the second is not run. We can also simplify the ternery conditional ( ?: ) to save a conditional.
  1. w.UpdateVisibility(vis, forceVisible || ((!clipped && !w.hideIfOffScreen) || IsVisible(w)));
  2.  

Not much else to do in that method, from my perspective. So we move on to UIWidget.UpdateTransform (following the method structure).

  1. public bool UpdateTransform (int frame)
  2. {
  3.         Transform trans = cachedTransform;
  4.         mPlayMode = Application.isPlaying;
  5.  
  6. #if UNITY_EDITOR
  7.         if (mMoved || !mPlayMode)
  8. #else
  9.         if (mMoved)
  10. #endif
  11.         {
  12.                 mMoved = true;
  13.                 mMatrixFrame = -1;
  14.                 trans.hasChanged = false;
  15.                 Vector2 offset = pivotOffset;
  16.  
  17.                 float x0 = -offset.x * mWidth;
  18.                 float y0 = -offset.y * mHeight;
  19.                 float x1 = x0 + mWidth;
  20.                 float y1 = y0 + mHeight;
  21.  
  22.                 mOldV0 = panel.worldToLocal.MultiplyPoint3x4(trans.TransformPoint(x0, y0, 0f));
  23.                 mOldV1 = panel.worldToLocal.MultiplyPoint3x4(trans.TransformPoint(x1, y1, 0f));
  24.         }
  25.         else if (!panel.widgetsAreStatic && trans.hasChanged)
  26.         {
  27.                 mMoved = true;
  28.                 mMatrixFrame = -1;
  29.                 trans.hasChanged = false;
  30.                 Vector2 offset = pivotOffset;
  31.  
  32.                 float x0 = -offset.x * mWidth;
  33.                 float y0 = -offset.y * mHeight;
  34.                 float x1 = x0 + mWidth;
  35.                 float y1 = y0 + mHeight;
  36.  
  37.                 Vector3 v0 = panel.worldToLocal.MultiplyPoint3x4(trans.TransformPoint(x0, y0, 0f));
  38.                 Vector3 v1 = panel.worldToLocal.MultiplyPoint3x4(trans.TransformPoint(x1, y1, 0f));
  39.  
  40.                 if (Vector3.SqrMagnitude(mOldV0 - v0) > 0.000001f ||
  41.                         Vector3.SqrMagnitude(mOldV1 - v1) > 0.000001f)
  42.                 {
  43.                         mMoved = true;
  44.                         mOldV0 = v0;
  45.                         mOldV1 = v1;
  46.                 }
  47.         }
  48.  
  49.         // Notify the listeners
  50.         if (mMoved && onChange != null) onChange();
  51.         return mMoved || mChanged;
  52. }
  53.        

We can see in the picture above (although it's slightly cut off) that we call Vector3.SqrMagnitude which means, we're in the second condition in this instance - which we would expect, as none of the individual widgets have moved and only the parent panel is scrolling. Now, I think it's a bit weird that it even gets in here, but the trans.hasChanged must take the parent into account, which is a little annoying as it causes overhead for us here.

If you KNOW your widgets will not change while scrolling, you can set the panel to be static (widgetsAreStatic), which will escape all the calculations above.

I'm wondering if the first mMoved = true should actually be mMoved = false, as there is a second check for movement with the sqrmagnitude. That would potentially save whatever is hooked up to the OnChange and the stuff back in UIPanel. I'll test this out later. Also, we don't use the frame parameter at all - I imagine this is a leftover.

Moving on to UIWidget.UpdateGeometry

  1. public bool UpdateGeometry (int frame)
  2. {
  3.         // Has the alpha changed?
  4.         float finalAlpha = CalculateFinalAlpha(frame);
  5.         if (mIsVisibleByAlpha && mLastAlpha != finalAlpha) mChanged = true;
  6.         mLastAlpha = finalAlpha;
  7.  
  8.         if (mChanged)
  9.         {
  10.                 mChanged = false;
  11.  
  12.                 if (mIsVisibleByAlpha && finalAlpha > 0.001f && shader != null)
  13.                 {
  14.                         bool hadVertices = geometry.hasVertices;
  15.  
  16.                         if (fillGeometry)
  17.                         {
  18.                                 geometry.Clear();
  19.                                 OnFill(geometry.verts, geometry.uvs, geometry.cols);
  20.                         }
  21.  
  22.                         if (geometry.hasVertices)
  23.                         {
  24.                                 // Want to see what's being filled? Uncomment this line.
  25.                                 //Debug.Log("Fill " + name + " (" + Time.frameCount + ")");
  26.  
  27.                                 if (mMatrixFrame != frame)
  28.                                 {
  29.                                         mLocalToPanel = panel.worldToLocal * cachedTransform.localToWorldMatrix;
  30.                                         mMatrixFrame = frame;
  31.                                 }
  32.                                 geometry.ApplyTransform(mLocalToPanel);
  33.                                 mMoved = false;
  34.                                 return true;
  35.                         }
  36.                         return hadVertices;
  37.                 }
  38.                 else if (geometry.hasVertices)
  39.                 {
  40.                         if (fillGeometry) geometry.Clear();
  41.                         mMoved = false;
  42.                         return true;
  43.                 }
  44.         }
  45.         else if (mMoved && geometry.hasVertices)
  46.         {
  47.                 if (mMatrixFrame != frame)
  48.                 {
  49.                         mLocalToPanel = panel.worldToLocal * cachedTransform.localToWorldMatrix;
  50.                         mMatrixFrame = frame;
  51.                 }
  52.                 geometry.ApplyTransform(mLocalToPanel);
  53.                 mMoved = false;
  54.                 return true;
  55.         }
  56.         mMoved = false;
  57.         return false;
  58. }
  59.  

First, we saw in the deep profiler that the two culprits are the method itself and the ApplyTransform, which means we can largely ignore any other method calls inside the method.
The Matrix4x4 multiply operator leads us to the condition:

  1. else if (mMoved && geometry.hasVertices)

Remember that the mMatrixFrame was set to -1 in the UpdateTransform and mMoved was set to true, this means that this one will always run assuming the widget has vertices  (Sprites, labels). There doesn't seem to be any way to improve this code directly, apart from avoiding some of the calls ealier by not having mMoved set if something hasn't moved.

Looking inside the UIGeometry.ApplyTransform

  1. public void ApplyTransform (Matrix4x4 widgetToPanel)
  2. {
  3.         if (verts.size > 0)
  4.         {
  5.                 mRtpVerts.Clear();
  6.                 for (int i = 0, imax = verts.size; i < imax; ++i) mRtpVerts.Add(widgetToPanel.MultiplyPoint3x4(verts[i]));
  7.  
  8.                 // Calculate the widget's normal and tangent
  9.                 mRtpNormal = widgetToPanel.MultiplyVector(Vector3.back).normalized;
  10.                 Vector3 tangent = widgetToPanel.MultiplyVector(Vector3.right).normalized;
  11.                 mRtpTan = new Vector4(tangent.x, tangent.y, tangent.z, -1f);
  12.         }
  13.         else mRtpVerts.Clear();
  14. }
  15.  
The relative-to-panel vertices, normals and tangents are calculated and saved - doing this every frame means Clearing a list and refiling it with these. Only thing I can see as a potential optimization is to avoid generating normals and tangents unless they are needed (set in the UIPanel drawing the widget). Other than that, the best optimization is to avoid re-applying the transform at all.


Applying improvements
Using times on UIPanel.LateUpdate from a regular profiler (not deep)
Stock: 1.75 - 2.0ms
move time.frameCount: no significant difference
UpdateWidgets optimizations:
simplify conditional: 1.80+ ms slightly slower (boo), removed again.
UpdateTransform optimizations:
mMoved default to false : 0.45 - 1.40ms - A significant improvement. This may have sideeffects, however.
UpdateGeometry optimizations:
pass generateNormals to ApplyTransform: no significant difference.
UIPanel Inspector optimizations:
Turning on Static: 0.20 - 0.60ms A very significant improvement.


Before drawing any conclusions, these numbers assume the computer I am using, the particulars of what's running in the background etc. This means you cannot compare the raw numbers with anything else than other tests from the same setup. I've also only tested with some 200 elements in the list, and not tested the scaling of it (say to 2000). That said, the internal percentages do speak for themselves given these caveats.

1) There is an optimization to be had in changing the UIWidget.UpdateTransform to not set mMoved every frame unless the transform itself has moved or changed - this needs some more tests, to make sure it doesn't break functionality.
2) If you know your widgets does not change while scrolling you can switch the widgetsAreStatic to true on the UIPanel to gain significant performace increase.

This has been a fun little trip debugging performance; I'll test the potential optimizations out some more in other scenarios and make sure they don't break anything, then pullrequest it to the main trunk.

newlife

  • Newbie
  • *
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 23
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #35 on: May 16, 2015, 06:45:15 PM »
Nicki,
I read your post carefully.
You made an extensive testing, but your conclusion (turning on Static on panel) is something almost obvious.
You also forgot to make a compare with 2.7 performances, which are the target performance to aim.
Even with static on, performance differences are HUGE.
Today I tested my app (both old and new version) on a Samsung Galaxy S4 mini, which is a good performance device.
Performance differences on big draggable panel are extremely evident (old version very smooth, new version a lot of breaks in the scroll).

I think that the reason why there aren't a lot of people complaining about this is cause few people use big draggable panel and / or test they app on powerful devices.
Anyway I think that this is a major issue in NGUI 3.x, cause it implies that there must be something wrong in the code.

I really hope to hear ArenMook thought about this, hopefully not a conservative reply.

Thanks,
Michele Di Lena

r.pedra

  • Full Member
  • ***
  • Thank You
  • -Given: 7
  • -Receive: 20
  • Posts: 131
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #36 on: May 18, 2015, 03:34:10 AM »
Yeah, we have this problem with a ScrollPanel with 180 widgets underneath. You can see a big mountain in the profiler when scrolling. It's worse on the device (LG G3, which is powerful).
The only solution to this problem is to give the user an option to disable objects in the scrollview(this is a map with planes moving in realtime). But you can not do this for every scrollview. (Change to static didn't do anything for us)

Nicki

  • Global Moderator
  • Hero Member
  • *****
  • Thank You
  • -Given: 33
  • -Receive: 141
  • Posts: 1,768
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #37 on: May 22, 2015, 02:44:27 PM »
Hey Michele & r.pedra,

Thanks for reading. I sorta felt the "use static" would be painfully obvious, but I mainly wanted to show the numbers associated to make it clearer just how much it helps.

I purposely didn't make a comparison with 2.7, because there have been so many underlying changes that it's not a fair comparison - performance in certain areas, like huge scroll lists, have been sacrificed in order to have explicit ordering the way it is now and other feature improvements.

There are ways to combat this, by not just putting all objects in but instead using containers which you fill with information when they're supposed to draw - then you're generally limited to <10 containers at any one time vastly increasing your performance (and making unlimited scroll lists possible).

NGUI does not do this internally at this point, and it's up to the individual developer to implement this. Without changing the scrollviews fundamentally, it's not something that's easily done on NGUI's end.

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #38 on: May 22, 2015, 05:34:14 PM »
I can look into this in a bit, but right now I am extremely busy with Windward. It just launched on Steam and has been a very hectic experience.

r.pedra

  • Full Member
  • ***
  • Thank You
  • -Given: 7
  • -Receive: 20
  • Posts: 131
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #39 on: May 27, 2015, 11:29:17 AM »
Yes I saw that on Steam, front page , that's awesome ! You're like super rich now ahah :P

r.pedra

  • Full Member
  • ***
  • Thank You
  • -Given: 7
  • -Receive: 20
  • Posts: 131
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #40 on: May 27, 2015, 11:37:14 AM »
Hey Michele & r.pedra,

Thanks for reading. I sorta felt the "use static" would be painfully obvious, but I mainly wanted to show the numbers associated to make it clearer just how much it helps.

I purposely didn't make a comparison with 2.7, because there have been so many underlying changes that it's not a fair comparison - performance in certain areas, like huge scroll lists, have been sacrificed in order to have explicit ordering the way it is now and other feature improvements.

There are ways to combat this, by not just putting all objects in but instead using containers which you fill with information when they're supposed to draw - then you're generally limited to <10 containers at any one time vastly increasing your performance (and making unlimited scroll lists possible).

NGUI does not do this internally at this point, and it's up to the individual developer to implement this. Without changing the scrollviews fundamentally, it's not something that's easily done on NGUI's end.

Hi don't think our use case is compatible with your solution.

Basically You have a map:

----------
|          |
|          |
|          |
----------

But the user only see a part of this map and can scroll on it.
On this map, planes are moving along path rendered with Vectrosity(that literally have no impact on the scrollview perf).
What you suggest is to move the planes into multiple Panel is that right? It will increase drawcall no? Is the drawcall increase going to make it worse?

PS: I link an image of what we're doing, this way, you will understand it better

yuewah

  • Full Member
  • ***
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 180
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #41 on: May 30, 2015, 10:00:21 AM »
@r.pedra, is the plane contains other UI components ? e.g. UILabel, UIButton ?? If not, you should use other method if you want to optimise it, e.g. Particle System.

r.pedra

  • Full Member
  • ***
  • Thank You
  • -Given: 7
  • -Receive: 20
  • Posts: 131
    • View Profile
Re: Huge performance drop after updatating from 2.7.0 to 3.7.5
« Reply #42 on: June 01, 2015, 03:32:51 AM »
Not a bad idea. I'll work on it