_gchandle list final

This commit is contained in:
2023-08-27 14:59:59 +03:00
parent 3df1e86ecb
commit 20a92a994e

View File

@@ -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<IntPtr, GCHandle> persistentPool = new();
//private static Dictionary<IntPtr, GCHandle> 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<IntPtr, object> weakPool = new();
private static Dictionary<IntPtr, object> 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<object> weakPool = new();
private static ulong weakPoolOffset = weakHandleAccumulator + 1;
private static List<object> weakPoolOther = new();
private static ulong weakPoolOtherOffset = weakHandleAccumulator + 1;
private static int nextWeakPoolGCCollection;
private static long lastWeakPoolCollectionTime;
@@ -445,65 +440,45 @@ namespace FlaxEngine.Interop
/// </summary>
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<object> 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<object>(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<object> weakPool = ManagedHandlePool.weakPool;
List<object> 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<object> weakPool = ManagedHandlePool.weakPool;
List<object> 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");*/
}
}
}