1. 程式人生 > >uGUI Text富文字的頂點數優化的優化

uGUI Text富文字的頂點數優化的優化

於是想測試下自己寫的頂點數優化元件對GC的影響,不測不知道,一測嚇一跳 將下面的富文字複製30份填入一個Text中,在Text下掛上UIVertexOptimize元件 <size=30><b><color=#000000ff>1</color></b></size> 開啟Profiler工具,執行一看,每次Text更新,OptimizeVert函式都會產生高達12M多的堆分配

展開可以看到最主要的堆分配是在
可以看出在呼叫Linq下的去重擴充套件函式Distinct時會進行元素比較,由於比較的元素型別Triangle是值型別,預設的值型別比較器會有裝箱的操作,所以導致了大量的堆分配 擴充套件函式Distinct還有個帶比較器引數的過載版本,於是建立一個Triangle型別,的比較器 class TriangleCompare : IEqualityComparer<Triangle>     {         public bool Equals(Triangle x, Triangle y)         {             return UIVertexEquals(x.v1, y.v1) && UIVertexEquals(x.v2, y.v2) && UIVertexEquals(x.v3, y.v3);         }         public int GetHashCode(Triangle obj)         {             return GetUIVertexHashCode(obj.v1)                    ^ GetUIVertexHashCode(obj.v2)                    ^ GetUIVertexHashCode(obj.v3);         }         int GetUIVertexHashCode(UIVertex vertex)         {             return vertex.color.a.GetHashCode()                 ^ vertex.color.b.GetHashCode()                 ^ vertex.color.g.GetHashCode()                 ^ vertex.color.r.GetHashCode()                    ^ vertex.normal.GetHashCode()                    ^ vertex.position.GetHashCode()                    ^ vertex.tangent.GetHashCode()                    ^ vertex.uv0.GetHashCode()                    ^ vertex.uv1.GetHashCode();         }         bool UIVertexEquals(UIVertex x, UIVertex y)         {             return x.color.a == y.color.a                    && x.color.b == y.color.b                    && x.color.g == y.color.g                    && x.color.r == y.color.r                    && x.normal == y.normal                    && x.position == y.position                    && x.tangent == y.tangent                    && x.uv1 == y.uv1                    && x.uv0 == y.uv0;         }     } 去重函式改為呼叫帶參版本 vertices = tris.Distinct(new TriangleCompare()).SelectMany(tri =>             new[]{                 tri.v1,                 tri.v2,                 tri.v3             }).ToList(); 重新執行堆分配降為了1M多

1M多還是非常誇張,繼續檢視堆分配最多的地方
可以看出往List中新增元素時,List會先判斷當前的容量是否足夠大,如果不夠,會將容量擴大為當前的兩倍,這個操作會有相應大小的堆分配 這個堆分配是免不了的,不過原先List<Triangle> tris作為區域性變數,分配的堆記憶體在OptimizeVert函式執行完後就變為垃圾記憶體了,並在下次執行時又重複建立重複擴容,可以將tris變數提升為成員變數以避免不必要的重複堆分配 最終元件修改為 using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; public class UIVertexOptimize : BaseMeshEffect {     struct Triangle     {         public UIVertex v1;         public UIVertex v2;         public UIVertex v3;     }     class TriangleCompare : IEqualityComparer<Triangle>     {         public bool Equals(Triangle x, Triangle y)         {             return UIVertexEquals(x.v1, y.v1) && UIVertexEquals(x.v2, y.v2) && UIVertexEquals(x.v3, y.v3);         }         public int GetHashCode(Triangle obj)         {             return GetUIVertexHashCode(obj.v1)                    ^ GetUIVertexHashCode(obj.v2)                    ^ GetUIVertexHashCode(obj.v3);         }         int GetUIVertexHashCode(UIVertex vertex)         {             return vertex.color.a.GetHashCode()                 ^ vertex.color.b.GetHashCode()                 ^ vertex.color.g.GetHashCode()                 ^ vertex.color.r.GetHashCode()                    ^ vertex.normal.GetHashCode()                    ^ vertex.position.GetHashCode()                    ^ vertex.tangent.GetHashCode()                    ^ vertex.uv0.GetHashCode()                    ^ vertex.uv1.GetHashCode();         }         bool UIVertexEquals(UIVertex x, UIVertex y)         {             return x.color.a == y.color.a                    && x.color.b == y.color.b                    && x.color.g == y.color.g                    && x.color.r == y.color.r                    && x.normal == y.normal                    && x.position == y.position                    && x.tangent == y.tangent                    && x.uv1 == y.uv1                    && x.uv0 == y.uv0;         }     }     List<UIVertex> verts = new List<UIVertex>();     List<Triangle> tris = new List<Triangle>();     public override void ModifyMesh(VertexHelper vh)     {         vh.GetUIVertexStream(verts);         Debug.Log(verts.Count);         OptimizeVert(ref verts);         Debug.Log(verts.Count);         vh.Clear();         vh.AddUIVertexTriangleStream(verts);     }     void OptimizeVert(ref List<UIVertex> vertices)     {         if (tris.Capacity < vertices.Count / 3)         {             tris.Capacity = vertices.Count;         }         for (int i = 0; i <= vertices.Count - 3; i += 3)         {             tris.Add(new Triangle() { v1 = vertices[i], v2 = vertices[i + 1], v3 = vertices[i + 2] });         }         vertices.Clear();         vertices.AddRange(tris.Distinct(new TriangleCompare()).SelectMany(t => new[]         {             t.v1,             t.v2,             t.v3         }));         tris.Clear();     } } 其中List的Clear操作不會影響到List的容量大小 Distinct擴充套件函式還是會產生可觀的堆分配,是因為其使用了臨時HashSet容器,跟List一樣在容量變化時會有堆分配,所以在一些頻繁呼叫的地方需要實現一個無GC的去重函式 還有兩個迭代操作產生的幾百B的堆分配,這就是Unity使用的Mono編譯器令人詬病已久的foreach問題 總結為三個問題 1.值型別的預設比較會有裝箱操作,這個不確定是普遍的問題還是Unity的Mono編譯器的問題,在值型別裡重寫GetHashCode
Equals函式或寫個對應的比較器類可解決 2.容器類的擴容問題,特別是臨時容器變數,最好實現一個公用的容器物件池避免重複建立容器 ,Linq的擴充套件函式中可能有大部分會用到臨時容器變數,這些臨時容器擴容時可能產生巨量的堆分配,並在函式執行完後直接變為垃圾記憶體,所以在頻繁呼叫的地方最好不用Linq操作 3.foreach迭代操作的問題,Unity5.5版本說明中說已經修復了該問題,未驗證