From 20a92a994eae1c6bc60cc076dadf702e1a120369 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sun, 27 Aug 2023 14:59:59 +0300 Subject: [PATCH] _gchandle list final --- Source/Engine/Engine/NativeInterop.Managed.cs | 311 ++++++------------ 1 file changed, 92 insertions(+), 219 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Managed.cs b/Source/Engine/Engine/NativeInterop.Managed.cs index e2a46b348..8cb5674f3 100644 --- a/Source/Engine/Engine/NativeInterop.Managed.cs +++ b/Source/Engine/Engine/NativeInterop.Managed.cs @@ -3,6 +3,7 @@ #if USE_NETCORE using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; @@ -413,30 +414,24 @@ namespace FlaxEngine.Interop internal static class ManagedHandlePool { + // This pool stores weak GCHandles in internal list to avoid huge amount of allocations in + // internal GCHandle pools. The object lifetime of weak handle needs to survive at least few + // garbage collection cycles to avoid objects getting collected in the middle of marshalling + // operations. + + // Tuning parameters for when weak pools should be recycled private const int WeakPoolCollectionSizeThreshold = 10000000; private const int WeakPoolCollectionTimeThreshold = 500; - // Rolling numbers for handles, two bits reserved for the type - //private static ulong normalHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000; - //private static ulong pinnedHandleAccumulator = ((ulong)GCHandleType.Pinned << 62) & 0xC000000000000000; - private static ulong weakHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000; - - // Dictionaries for storing the valid handles. - // Note: Using locks seems to be generally the fastest when adding or fetching from the dictionary. - // Concurrent dictionaries could also be considered, but they perform much slower when adding to the dictionary. - //private static Dictionary persistentPool = new(); - //private static Dictionary pinnedPool = new(); - - // TODO: Performance of pinned handles are poor at the moment due to GCHandle wrapping. - // TODO: .NET8: Experiment with pinned arrays for faster pinning: https://github.com/dotnet/runtime/pull/89293 + // Rolling numbers for weak handles, two upper bits reserved for marking the weak handle + private static ulong weakHandleAccumulator = ((ulong)2 << 62) & 0xC000000000000000; // Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles. - // Periodically when the pools are being accessed and conditions are met, the other pool is cleared and swapped. - private static Dictionary weakPool = new(); - private static Dictionary weakPoolOther = new(); - private static object weakPoolLock = new object(); - private static ulong nextWeakPoolCollection; - private static ulong weakPoolCycle; + // Periodically the pools are cleared and swapped, letting GC collect the cleared objects. + private static List weakPool = new(); + private static ulong weakPoolOffset = weakHandleAccumulator + 1; + private static List weakPoolOther = new(); + private static ulong weakPoolOtherOffset = weakHandleAccumulator + 1; private static int nextWeakPoolGCCollection; private static long lastWeakPoolCollectionTime; @@ -445,65 +440,45 @@ namespace FlaxEngine.Interop /// internal static void TryCollectWeakHandles() { - nextWeakPoolCollection++; - if (nextWeakPoolCollection < 60) + // Try to swap pools after garbage collection or whenever the pool gets too large + var gc0CollectionCount = GC.CollectionCount(0); + if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) return; + nextWeakPoolGCCollection = gc0CollectionCount + 1; - nextWeakPoolCollection = 0; + // Prevent huge allocations from swapping the pools in the middle of the operation + if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) + return; + lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp(); - ulong currentCycle = weakPoolCycle; - - lock (weakPoolLock) - { - if (currentCycle != weakPoolCycle) - return; - - weakPoolCycle++; - - // Try to swap pools after garbage collection or whenever the pool gets too large - var gc0CollectionCount = GC.CollectionCount(0); - if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold) - return; - nextWeakPoolGCCollection = gc0CollectionCount + 1; - - // Prevent huge allocations from swapping the pools in the middle of the operation - if (System.Diagnostics.Stopwatch.GetElapsedTime(lastWeakPoolCollectionTime).TotalMilliseconds < WeakPoolCollectionTimeThreshold) - return; - lastWeakPoolCollectionTime = System.Diagnostics.Stopwatch.GetTimestamp(); - - // Swap the pools and release the oldest pool for GC - (weakPool, weakPoolOther) = (weakPoolOther, weakPool); - weakPool.Clear(); - //FlaxEditor.Editor.LogWarning("collected handles"); - } + // Swap the pools and release the oldest pool for GC + foreach (ref object item in CollectionsMarshal.AsSpan(weakPoolOther)) + item = null; + weakPoolOtherOffset = weakHandleAccumulator + 1; + (weakPool, weakPoolOther) = (weakPoolOther, weakPool); + (weakPoolOffset, weakPoolOtherOffset) = (weakPoolOtherOffset, weakPoolOffset); } - /*[MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IntPtr NewHandle(GCHandleType type) => type switch - { - GCHandleType.Normal => (IntPtr)Interlocked.Increment(ref normalHandleAccumulator), - GCHandleType.Pinned => (IntPtr)Interlocked.Increment(ref pinnedHandleAccumulator), - GCHandleType.Weak => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator), - GCHandleType.WeakTrackResurrection => (IntPtr)Interlocked.Increment(ref weakHandleAccumulator), - _ => throw new NotImplementedException(type.ToString()) - };*/ - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //private static GCHandleType GetHandleType(IntPtr handle) => (GCHandleType)(((ulong)handle & 0xC000000000000000) >> 62); - + // Returns true if the handle contains the weak handle marker. + // Note: This assumes internal GCHandles are not stored as 32-bit values, so the upper bits are never used by runtime. private static bool IsWeakHandle(IntPtr handle) => ((ulong)handle & 0xC000000000000000) >> 62 == 2; + private static int GetWeakHandleIndex(IntPtr handle, ulong offset) => (int)((ulong)handle - offset); + internal static IntPtr AllocateHandle(object value, GCHandleType type) { if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection) { + // Store the values and references locally in case pool gets swapped now + List weakPool = ManagedHandlePool.weakPool; + ulong weakPoolOffset = ManagedHandlePool.weakPoolOffset; - lock (weakPoolLock) - { - IntPtr handle = (IntPtr)(++weakHandleAccumulator); - weakPool.Add(handle, value); - return handle; - } + IntPtr handle = (IntPtr)Interlocked.Increment(ref weakHandleAccumulator); + int handleIndex = GetWeakHandleIndex(handle, weakPoolOffset); + if (handleIndex >= weakPool.Count) + weakPool.AddRange(Enumerable.Repeat(null, 10000)); + weakPool[handleIndex] = value; + return handle; } else { @@ -511,196 +486,94 @@ namespace FlaxEngine.Interop IntPtr handle = GCHandle.ToIntPtr(gcHandle); return handle; } - /*IntPtr handle = NewHandle(type); - switch (type) - { - case GCHandleType.Normal: - lock (persistentPool) - persistentPool.Add(handle, value); - break; - case GCHandleType.Pinned: - lock (pinnedPool) - pinnedPool.Add(handle, GCHandle.Alloc(value, GCHandleType.Pinned)); - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - weakPool.Add(handle, value); - } - break; - } - return handle;*/ } internal static object GetObject(IntPtr handle) { if (IsWeakHandle(handle)) - lock (weakPoolLock) + { + // Store the values and references locally in case pool gets swapped now + List weakPool = ManagedHandlePool.weakPool; + List weakPoolOther = ManagedHandlePool.weakPool; + ulong weakPoolOffset = ManagedHandlePool.weakPoolOffset; + ulong weakPoolOtherOffset = ManagedHandlePool.weakPoolOtherOffset; + + if ((ulong)handle > weakHandleAccumulator) { - if (weakPool.TryGetValue(handle, out object weakValue) || weakPoolOther.TryGetValue(handle, out weakValue)) - { - return weakValue; - } } + else if ((ulong)handle >= weakPoolOffset) + { + int index = GetWeakHandleIndex(handle, weakPoolOffset); + return weakPool[index]; + } + else if ((ulong)handle >= weakPoolOtherOffset) + { + int index = GetWeakHandleIndex(handle, weakPoolOtherOffset); + return weakPoolOther[index]; + } + + throw new NativeInteropException("Invalid weak ManagedHandle"); + } GCHandle gcHandle = GCHandle.FromIntPtr(handle); if (!gcHandle.IsAllocated) - throw new NativeInteropException("Invalid ManagedHandle"); + throw new NativeInteropException("Invalid ManagedHandle, the handle is not allocated"); return gcHandle.Target; - - /*switch (GetHandleType(handle)) - { - case GCHandleType.Normal: - lock (persistentPool) - { - if (persistentPool.TryGetValue(handle, out object value)) - return value; - } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - if (pinnedPool.TryGetValue(handle, out GCHandle gcHandle)) - return gcHandle.Target; - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - if (weakPool.TryGetValue(handle, out object value)) - return value; - else if (weakPoolOther.TryGetValue(handle, out value)) - return value; - } - break; - } - throw new NativeInteropException("Invalid ManagedHandle");*/ } internal static void SetObject(IntPtr handle, object value) { if (IsWeakHandle(handle)) - lock (weakPoolLock) + { + // Store the values and references locally in case pool gets swapped now + List weakPool = ManagedHandlePool.weakPool; + List weakPoolOther = ManagedHandlePool.weakPool; + ulong weakPoolOffset = ManagedHandlePool.weakPoolOffset; + ulong weakPoolOtherOffset = ManagedHandlePool.weakPoolOtherOffset; + + if ((ulong)handle > weakHandleAccumulator) { - if (weakPool.ContainsKey(handle)) - { - weakPool[handle] = value; - return; - } - else if (weakPoolOther.ContainsKey(handle)) - { - weakPoolOther[handle] = value; - return; - } } + else if ((ulong)handle >= weakPoolOffset) + { + int index = GetWeakHandleIndex(handle, weakPoolOffset); + weakPool[index] = value; + return; + } + else if ((ulong)handle >= weakPoolOtherOffset) + { + int index = GetWeakHandleIndex(handle, weakPoolOtherOffset); + weakPoolOther[index] = value; + return; + } + + throw new NativeInteropException("Invalid weak ManagedHandle"); + } GCHandle gcHandle = GCHandle.FromIntPtr(handle); if (!gcHandle.IsAllocated) - throw new NativeInteropException("Invalid ManagedHandle"); + throw new NativeInteropException("Invalid ManagedHandle, the handle is not allocated"); gcHandle.Target = value; - - /*switch (GetHandleType(handle)) - { - case GCHandleType.Normal: - lock (persistentPool) - { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(persistentPool, handle); - if (!Unsafe.IsNullRef(ref obj)) - { - obj = value; - return; - } - } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - ref GCHandle gcHandle = ref CollectionsMarshal.GetValueRefOrNullRef(pinnedPool, handle); - if (!Unsafe.IsNullRef(ref gcHandle)) - { - gcHandle.Target = value; - return; - } - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - { - TryCollectWeakHandles(); - { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPool, handle); - if (!Unsafe.IsNullRef(ref obj)) - { - obj = value; - return; - } - } - { - ref object obj = ref CollectionsMarshal.GetValueRefOrNullRef(weakPoolOther, handle); - if (!Unsafe.IsNullRef(ref obj)) - { - obj = value; - return; - } - } - } - break; - } - throw new NativeInteropException("Invalid ManagedHandle");*/ } internal static void FreeHandle(IntPtr handle) { if (IsWeakHandle(handle)) - lock (weakPoolLock) - { - if (weakPool.ContainsKey(handle) || weakPoolOther.ContainsKey(handle)) - return; - } + { + if ((ulong)handle >= weakPoolOtherOffset && (ulong)handle <= weakHandleAccumulator) + return; + } GCHandle gcHandle = GCHandle.FromIntPtr(handle); if (!gcHandle.IsAllocated) - throw new NativeInteropException("Invalid ManagedHandle"); + throw new NativeInteropException("Invalid ManagedHandle, the handle is not allocated"); gcHandle.Free(); - //if (weakPool.Contains(handle) || ) - - /*switch (GetHandleType(handle)) - { - case GCHandleType.Normal: - lock (persistentPool) - { - if (persistentPool.Remove(handle)) - return; - } - break; - case GCHandleType.Pinned: - lock (pinnedPool) - { - if (pinnedPool.Remove(handle, out GCHandle gcHandle)) - { - gcHandle.Free(); - return; - } - } - break; - case GCHandleType.Weak: - case GCHandleType.WeakTrackResurrection: - lock (weakPoolLock) - TryCollectWeakHandles(); - return; - } - throw new NativeInteropException("Invalid ManagedHandle");*/ } } }