_gchandle list final
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
#if USE_NETCORE
|
#if USE_NETCORE
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -413,30 +414,24 @@ namespace FlaxEngine.Interop
|
|||||||
|
|
||||||
internal static class ManagedHandlePool
|
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 WeakPoolCollectionSizeThreshold = 10000000;
|
||||||
private const int WeakPoolCollectionTimeThreshold = 500;
|
private const int WeakPoolCollectionTimeThreshold = 500;
|
||||||
|
|
||||||
// Rolling numbers for handles, two bits reserved for the type
|
// Rolling numbers for weak handles, two upper bits reserved for marking the weak handle
|
||||||
//private static ulong normalHandleAccumulator = ((ulong)GCHandleType.Normal << 62) & 0xC000000000000000;
|
private static ulong weakHandleAccumulator = ((ulong)2 << 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
|
|
||||||
|
|
||||||
// Manage double-buffered pool for weak handles in order to avoid collecting in-flight handles.
|
// 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.
|
// Periodically the pools are cleared and swapped, letting GC collect the cleared objects.
|
||||||
private static Dictionary<IntPtr, object> weakPool = new();
|
private static List<object> weakPool = new();
|
||||||
private static Dictionary<IntPtr, object> weakPoolOther = new();
|
private static ulong weakPoolOffset = weakHandleAccumulator + 1;
|
||||||
private static object weakPoolLock = new object();
|
private static List<object> weakPoolOther = new();
|
||||||
private static ulong nextWeakPoolCollection;
|
private static ulong weakPoolOtherOffset = weakHandleAccumulator + 1;
|
||||||
private static ulong weakPoolCycle;
|
|
||||||
private static int nextWeakPoolGCCollection;
|
private static int nextWeakPoolGCCollection;
|
||||||
private static long lastWeakPoolCollectionTime;
|
private static long lastWeakPoolCollectionTime;
|
||||||
|
|
||||||
@@ -445,65 +440,45 @@ namespace FlaxEngine.Interop
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static void TryCollectWeakHandles()
|
internal static void TryCollectWeakHandles()
|
||||||
{
|
{
|
||||||
nextWeakPoolCollection++;
|
// Try to swap pools after garbage collection or whenever the pool gets too large
|
||||||
if (nextWeakPoolCollection < 60)
|
var gc0CollectionCount = GC.CollectionCount(0);
|
||||||
|
if (gc0CollectionCount < nextWeakPoolGCCollection && weakPool.Count < WeakPoolCollectionSizeThreshold)
|
||||||
return;
|
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;
|
// Swap the pools and release the oldest pool for GC
|
||||||
|
foreach (ref object item in CollectionsMarshal.AsSpan(weakPoolOther))
|
||||||
lock (weakPoolLock)
|
item = null;
|
||||||
{
|
weakPoolOtherOffset = weakHandleAccumulator + 1;
|
||||||
if (currentCycle != weakPoolCycle)
|
(weakPool, weakPoolOther) = (weakPoolOther, weakPool);
|
||||||
return;
|
(weakPoolOffset, weakPoolOtherOffset) = (weakPoolOtherOffset, weakPoolOffset);
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
// Returns true if the handle contains the weak handle marker.
|
||||||
private static IntPtr NewHandle(GCHandleType type) => type switch
|
// Note: This assumes internal GCHandles are not stored as 32-bit values, so the upper bits are never used by runtime.
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
private static bool IsWeakHandle(IntPtr handle) => ((ulong)handle & 0xC000000000000000) >> 62 == 2;
|
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)
|
internal static IntPtr AllocateHandle(object value, GCHandleType type)
|
||||||
{
|
{
|
||||||
if (type == GCHandleType.Weak || type == GCHandleType.WeakTrackResurrection)
|
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)Interlocked.Increment(ref weakHandleAccumulator);
|
||||||
{
|
int handleIndex = GetWeakHandleIndex(handle, weakPoolOffset);
|
||||||
IntPtr handle = (IntPtr)(++weakHandleAccumulator);
|
if (handleIndex >= weakPool.Count)
|
||||||
weakPool.Add(handle, value);
|
weakPool.AddRange(Enumerable.Repeat<object>(null, 10000));
|
||||||
return handle;
|
weakPool[handleIndex] = value;
|
||||||
}
|
return handle;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -511,196 +486,94 @@ namespace FlaxEngine.Interop
|
|||||||
IntPtr handle = GCHandle.ToIntPtr(gcHandle);
|
IntPtr handle = GCHandle.ToIntPtr(gcHandle);
|
||||||
return handle;
|
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)
|
internal static object GetObject(IntPtr handle)
|
||||||
{
|
{
|
||||||
if (IsWeakHandle(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);
|
GCHandle gcHandle = GCHandle.FromIntPtr(handle);
|
||||||
|
|
||||||
if (!gcHandle.IsAllocated)
|
if (!gcHandle.IsAllocated)
|
||||||
throw new NativeInteropException("Invalid ManagedHandle");
|
throw new NativeInteropException("Invalid ManagedHandle, the handle is not allocated");
|
||||||
|
|
||||||
return gcHandle.Target;
|
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)
|
internal static void SetObject(IntPtr handle, object value)
|
||||||
{
|
{
|
||||||
if (IsWeakHandle(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.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);
|
GCHandle gcHandle = GCHandle.FromIntPtr(handle);
|
||||||
|
|
||||||
if (!gcHandle.IsAllocated)
|
if (!gcHandle.IsAllocated)
|
||||||
throw new NativeInteropException("Invalid ManagedHandle");
|
throw new NativeInteropException("Invalid ManagedHandle, the handle is not allocated");
|
||||||
|
|
||||||
gcHandle.Target = value;
|
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)
|
internal static void FreeHandle(IntPtr handle)
|
||||||
{
|
{
|
||||||
if (IsWeakHandle(handle))
|
if (IsWeakHandle(handle))
|
||||||
lock (weakPoolLock)
|
{
|
||||||
{
|
if ((ulong)handle >= weakPoolOtherOffset && (ulong)handle <= weakHandleAccumulator)
|
||||||
if (weakPool.ContainsKey(handle) || weakPoolOther.ContainsKey(handle))
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
GCHandle gcHandle = GCHandle.FromIntPtr(handle);
|
GCHandle gcHandle = GCHandle.FromIntPtr(handle);
|
||||||
|
|
||||||
if (!gcHandle.IsAllocated)
|
if (!gcHandle.IsAllocated)
|
||||||
throw new NativeInteropException("Invalid ManagedHandle");
|
throw new NativeInteropException("Invalid ManagedHandle, the handle is not allocated");
|
||||||
|
|
||||||
gcHandle.Free();
|
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");*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user