[NUI] Dispose queue incrementally
authorEunki Hong <eunkiki.hong@samsung.com>
Wed, 6 Dec 2023 16:12:33 +0000 (01:12 +0900)
committerEunki Hong <h.pichulia@gmail.com>
Wed, 13 Dec 2023 01:43:33 +0000 (10:43 +0900)
Let we dispose IDisposable incrementally, not immediately.

It will reduce the main thread blocking during Dispose by GC.

To make the behavior didn't changed as before, let we make
FullGC as defalt. And turn on incremental GC feature if someone need.

Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
src/Tizen.NUI/src/internal/Application/Application.cs
src/Tizen.NUI/src/internal/Common/DisposeQueue.cs
src/Tizen.NUI/src/internal/Common/ProcessorController.cs
src/Tizen.NUI/src/internal/Interop/Interop.ProcessorController.cs

index d66cd5320d3c9100494a0bacb356ee6c25579d39..cc9209eba43e9c3798ac24f35a1c04177588659a 100755 (executable)
@@ -684,6 +684,12 @@ namespace Tizen.NUI
             DisposeQueue.Instance.Initialize();
             Tizen.Tracer.End();
 
+            Log.Info("NUI", "[NUI] OnApplicationInit: ProcessorController Initialize");
+            Tizen.Tracer.Begin("[NUI] OnApplicationInit: ProcessorController Initialize");
+            // Initialize ProcessorController Singleton class. This is also required to create ProcessorController on main thread.
+            ProcessorController.Instance.Initialize();
+            Tizen.Tracer.End();
+
             Log.Info("NUI", "[NUI] OnApplicationInit: GetWindow");
             Tizen.Tracer.Begin("[NUI] OnApplicationInit: GetWindow");
             Window.Instance = GetWindow();
index 71ec43674edc30062c1cafb1835b48c4567c8390..c0ea1d1150534bf9f04676416e45fee131494ccb 100755 (executable)
@@ -18,6 +18,7 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 
 namespace Tizen.NUI
 {
@@ -27,10 +28,21 @@ namespace Tizen.NUI
         private static readonly DisposeQueue disposableQueue = new DisposeQueue();
         private List<IDisposable> disposables = new List<IDisposable>();
         private System.Object listLock = new object();
+
+        // Dispose incrementally at least max(100, incrementallyDisposedQueue * 20%) for each 1 event callback
+        private const long minimumIncrementalCount = 100;
+        private const long minimumIncrementalRate = 20;
+        private List<IDisposable> incrementallyDisposedQueue = new List<IDisposable>();
+
         private EventThreadCallback eventThreadCallback;
         private EventThreadCallback.CallbackDelegate disposeQueueProcessDisposablesDelegate;
 
-        private bool isCalled = false;
+        private bool initialied = false;
+        private bool processorRegistered = false;
+        private bool eventThreadCallbackTriggered = false;
+
+        private bool incrementalDisposeSupported = false;
+        private bool fullCollectRequested = false;
 
         private DisposeQueue()
         {
@@ -39,6 +51,7 @@ namespace Tizen.NUI
         ~DisposeQueue()
         {
             Tizen.Log.Debug("NUI", $"DisposeQueue is destroyed\n");
+            initialied = false;
         }
 
         public static DisposeQueue Instance
@@ -46,13 +59,25 @@ namespace Tizen.NUI
             get { return disposableQueue; }
         }
 
+        public bool IncrementalDisposeSupported
+        {
+            get => incrementalDisposeSupported;
+            set => incrementalDisposeSupported = value;
+        }
+
+        public bool FullyDisposeNextCollect
+        {
+            get => fullCollectRequested;
+            set => fullCollectRequested = value;
+        }
+
         public void Initialize()
         {
-            if (isCalled == false)
+            if (!initialied)
             {
                 disposeQueueProcessDisposablesDelegate = new EventThreadCallback.CallbackDelegate(ProcessDisposables);
                 eventThreadCallback = new EventThreadCallback(disposeQueueProcessDisposablesDelegate);
-                isCalled = true;
+                initialied = true;
 
                 DebugFileLogging.Instance.WriteLog("DiposeTest START");
             }
@@ -65,23 +90,87 @@ namespace Tizen.NUI
                 disposables.Add(disposable);
             }
 
-            if (eventThreadCallback != null)
+            if (initialied && eventThreadCallback != null && !eventThreadCallbackTriggered)
             {
+                eventThreadCallbackTriggered = true;
+                eventThreadCallback.Trigger();
+            }
+        }
+
+        public void TriggerProcessDisposables(object o, EventArgs e)
+        {
+            processorRegistered = false;
+
+            if (eventThreadCallback != null && !eventThreadCallbackTriggered)
+            {
+                eventThreadCallbackTriggered = true;
                 eventThreadCallback.Trigger();
             }
         }
 
         public void ProcessDisposables()
         {
+            eventThreadCallbackTriggered = false;
+
             lock (listLock)
             {
-                foreach (IDisposable disposable in disposables)
+                if (disposables.Count > 0)
                 {
-                    disposable.Dispose();
-                    DebugFileLogging.Instance.WriteLog($"disposable.Dispose(); type={disposable.GetType().FullName}, hash={disposable.GetHashCode()}");
+                    DebugFileLogging.Instance.WriteLog($"Newly add {disposables.Count} count of disposables. Total disposables count is {incrementallyDisposedQueue.Count + disposables.Count}.\n");
+                    // Move item from end, due to the performance issue.
+                    while (disposables.Count > 0)
+                    {
+                        var disposable = disposables.Last();
+                        disposables.RemoveAt(disposables.Count - 1);
+                        incrementallyDisposedQueue.Add(disposable);
+                    }
+                    disposables.Clear();
                 }
-                disposables.Clear();
             }
+
+            if (incrementallyDisposedQueue.Count > 0)
+            {
+                if (!incrementalDisposeSupported ||
+                    (!fullCollectRequested && !ProcessorController.Instance.Initialized))
+                {
+                    // Full Dispose if IncrementalDisposeSupported is false, or ProcessorController is not initialized yet.
+                    fullCollectRequested = true;
+                }
+                ProcessDisposablesIncrementally();
+            }
+        }
+
+        private void ProcessDisposablesIncrementally()
+        {
+            var disposeCount = fullCollectRequested ? incrementallyDisposedQueue.Count
+                                                    : Math.Min(incrementallyDisposedQueue.Count, Math.Max(minimumIncrementalCount, incrementallyDisposedQueue.Count * minimumIncrementalRate / 100));
+
+            DebugFileLogging.Instance.WriteLog((fullCollectRequested ? "Fully" : "Incrementally") + $" dispose {disposeCount} disposables. Will remained disposables count is {incrementallyDisposedQueue.Count - disposeCount}.\n");
+
+            fullCollectRequested = false;
+
+            // Dispose item from end, due to the performance issue.
+            while (disposeCount > 0 && incrementallyDisposedQueue.Count > 0)
+            {
+                --disposeCount;
+                var disposable = incrementallyDisposedQueue.Last();
+                incrementallyDisposedQueue.RemoveAt(incrementallyDisposedQueue.Count - 1);
+
+                DebugFileLogging.Instance.WriteLog($"disposable.Dispose(); type={disposable.GetType().FullName}, hash={disposable.GetHashCode()}");
+                disposable.Dispose();
+            }
+
+            if (incrementallyDisposedQueue.Count > 0)
+            {
+                if (ProcessorController.Instance.Initialized && !processorRegistered)
+                {
+                    processorRegistered = true;
+                    ProcessorController.Instance.ProcessorOnceEvent += TriggerProcessDisposables;
+                    ProcessorController.Instance.Awake();
+                }
+            }
+
+            DebugFileLogging.Instance.WriteLog($"Incrementally dispose finished.\n");
         }
     }
 }
index 8650dbed18fbc467d9fdda79a6fe9cff05789b3e..0132e90628289bdfa581104b0ec4b926e2ac5f1a 100755 (executable)
@@ -34,6 +34,7 @@ namespace Tizen.NUI
     internal sealed class ProcessorController : Disposable
     {
         private static ProcessorController instance = null;
+        private bool initialied = false;
 
         private ProcessorController() : this(Interop.ProcessorController.New(), true)
         {
@@ -41,13 +42,6 @@ namespace Tizen.NUI
 
         internal ProcessorController(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
         {
-            onceEventIndex = 0u;
-            internalProcessorOnceEvent = new EventHandler[2];
-            internalProcessorOnceEvent[0] = null;
-            internalProcessorOnceEvent[1] = null;
-
-            processorCallback = new ProcessorEventHandler(Process);
-            Interop.ProcessorController.SetCallback(SwigCPtr, processorCallback);
         }
 
         [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@@ -73,6 +67,8 @@ namespace Tizen.NUI
         public event EventHandler ProcessorEvent;
         public event EventHandler LayoutProcessorEvent;
 
+        public bool Initialized => initialied;
+
         public static ProcessorController Instance
         {
             get
@@ -85,6 +81,25 @@ namespace Tizen.NUI
             }
         }
 
+        public void Initialize()
+        {
+            if (initialied == false)
+            {
+                initialied = true;
+
+                Interop.ProcessorController.Initialize(SwigCPtr);
+
+                onceEventIndex = 0u;
+                internalProcessorOnceEvent = new EventHandler[2];
+                internalProcessorOnceEvent[0] = null;
+                internalProcessorOnceEvent[1] = null;
+
+                processorCallback = new ProcessorEventHandler(Process);
+                Interop.ProcessorController.SetCallback(SwigCPtr, processorCallback);
+                NDalicPINVOKE.ThrowExceptionIfExists();
+            }
+        }
+
         public void Process()
         {
             // Let us add once event into 1 index during 0 event invoke
@@ -130,6 +145,7 @@ namespace Tizen.NUI
         public void Awake()
         {
             Interop.ProcessorController.Awake(SwigCPtr);
+            NDalicPINVOKE.ThrowExceptionIfExists();
         }
     } // class ProcessorController
 } // namespace Tizen.NUI
index 8a8744c2601d851eb5b3e0aea7f2e527d0e0069a..628ff6dad7a56e6cd1f8a750f18757e69c3b4059 100755 (executable)
@@ -28,6 +28,9 @@ namespace Tizen.NUI
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_delete_ProcessorController")]
             public static extern global::System.IntPtr DeleteProcessorController(global::System.Runtime.InteropServices.HandleRef processorController);
 
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ProcessorController_Initialize")]
+            public static extern void Initialize(global::System.Runtime.InteropServices.HandleRef processorController);
+
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_ProcessorController_SetCallback")]
             public static extern void SetCallback(global::System.Runtime.InteropServices.HandleRef processorController, Tizen.NUI.ProcessorController.ProcessorEventHandler processorCallback);