[NUI] Dispose queue incrementally
authorEunki Hong <eunkiki.hong@samsung.com>
Wed, 6 Dec 2023 16:12:33 +0000 (01:12 +0900)
committerbshsqa <32317749+bshsqa@users.noreply.github.com>
Mon, 11 Dec 2023 06:49:58 +0000 (15:49 +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 d66cd53..cc9209e 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 71ec436..c0ea1d1 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 8650dbe..0132e90 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 8a8744c..628ff6d 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);