Why Is UGUI Taking 7ms in the Main Scene? Investigating High Rendering.UpdateBatches Cost in Unity
In the main scene report from GameOptim GOT Online, we noticed that UGUI consistently takes around 7ms, which is even higher than in the combat scene. From the call stack, a large portion of the cost appears to come from Rendering.UpdateBatches itself. Could this be caused by calling SetActive too frequently in the project? https://uwa overseas public.oss us east 1.aliyuncs.com/uploads/markdown/6c7f1b0c 8af4 4b13 a649 e4d5dbb06b07.png
Your direction is correct. There is a high probability that frequent calls are triggering SyncTransform , which is a very typical optimization pitfall in UGUI. However, while is one of the most common causes, it is not the only one. Operations such as RectTransform size changes , LayoutGroup rebuilds , ContentSizeFitter updates , and SetParent can also continuously trigger . Further confirmation still requires checking the call stack. The profiling path here is relatively clear: The main performance entry point of UGUI is Rendering.UpdateBatches , but the internal breakdown of this stage determines where the actual problem lies. Under normal circumstances, most of the cost is concentrated in the SendWillRenderCanvases node. But in this project report, the unusual part is that the self time of UpdateBatches itself accounts for an abnormally large portion, which points to a specific internal function: CanvasRenderer.SyncTransform Within a sampling window of 4000 frames , the number of calls remains around 300 . This call count is highly correlated with the self time percentage of : the higher the call count, the higher the self time, and the overall UGUI cost increases accordingly. It is important to note that a high call count is not the root cause by itself. It is the result of continuous TransformDirty generation. When a UI node’s active state, hierarchy, position, or size changes frequently, Unity continuously synchronizes Transform data. These changes are then propagated to the Canvas system, triggering batch rebuilds and Canvas updates. Therefore, when investigating , the focus should not only be on the cost itself, but on identifying what is continuously generating TransformDirty . Why does trigger ? When is called, the UI hierarchy containing that node triggers Transform synchronization and Canvas dirty flag updates. In complex Canvas hierarchies, this impact can spread to a large number of related UI elements. If certain UI elements are shown and hidden at high frequency—such as combat HUD floating damage text, status icons, or timers—every visibility change can cause the entire Canvas to resynchronize its Transforms. This causes the number of calls to continuously increase. A more subtle case occurs when frequently toggled UI elements share the same Canvas as static UI. In this case, every operation can force the static portion of the Canvas to be recalculated as well, creating a large amount of unnecessary rebuild overhead. Optimization Suggestions Use 0/1 instead of for high frequency visibility toggles For UI elements with high toggle frequency and short lifecycles such as floating damage text, status icons, and timers , switching between and can avoid continuous triggering. For medium to long lifecycle elements with high reuse frequency, object pooling should be prioritized. For cases where position and state need to be preserved and only temporary hiding is required, using an Alpha fade approach may be more appropriate. Separate dynamic and static UI Move frequently toggled UI nodes out of the main Canvas and place them under a dedicated Canvas. This ensures each visibility change only affects the smaller dynamic Canvas, while the static UI under the main Canvas remains untouched. Inspect nested Canvas coverage If a parent Canvas contains a large number of child elements, frequent updates in child nodes can still affect the parent. By combining the call count curve with scene level performance peaks, it is possible to trace back which Canvas is being repeatedly invalidated.