_gchandle list final
This commit is contained in:
@@ -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");*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user