From 22b4e25c02e96528da9af0a3ecd38ebefb50fc64 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 22 Dec 2022 17:01:45 +0200 Subject: [PATCH] Improve array allocations when marshalling method parameters --- .../Content/Import/TextureImportEntry.cs | 4 +- Source/Engine/Engine/NativeInterop.cs | 183 +++++++++++++----- Source/Engine/Engine/NativeInterop_Invoker.cs | 2 +- .../Bindings/BindingsGenerator.CSharp.cs | 18 +- 4 files changed, 142 insertions(+), 65 deletions(-) diff --git a/Source/Editor/Content/Import/TextureImportEntry.cs b/Source/Editor/Content/Import/TextureImportEntry.cs index abffe0ce7..7ea0157fe 100644 --- a/Source/Editor/Content/Import/TextureImportEntry.cs +++ b/Source/Editor/Content/Import/TextureImportEntry.cs @@ -391,8 +391,8 @@ namespace FlaxEditor.Content.Import MaxSize = managed.MaxSize, TextureGroup = managed.TextureGroup, Size = managed.Size, - SpriteAreas = managed.SpriteAreas?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteAreas)))) : IntPtr.Zero, - SpriteNames = managed.SpriteNames?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteNames)))) : IntPtr.Zero, + SpriteAreas = managed.SpriteAreas?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteAreas)))) : IntPtr.Zero, + SpriteNames = managed.SpriteNames?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteNames)))) : IntPtr.Zero, }; } internal static void Free(InternalOptionsNative unmanaged) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index e6184ebda..aff623f79 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -200,40 +200,85 @@ namespace FlaxEngine /// internal unsafe class ManagedArray { - private Array array; private GCHandle pinnedArrayHandle; private IntPtr unmanagedData; private int elementSize; private int length; - internal ManagedArray(Array arr) + internal static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr); + + /// + /// Returns an instance of ManagedArray from shared pool. + /// + /// + /// The resources must be released by calling FreePooled() instead of Free()-method. + /// + internal static ManagedArray WrapPooledArray(Array arr) { - array = arr; - unmanagedData = IntPtr.Zero; + ManagedArray managedArray = ManagedArrayPool.Get(); + managedArray.WrapArray(arr); + return managedArray; + } + + internal static ManagedArray AllocateNewArray(T* ptr, int length) where T : unmanaged => new ManagedArray(ptr, length, Unsafe.SizeOf()); + + internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, int elementSize) => new ManagedArray(ptr.ToPointer(), length, elementSize); + + /// + /// Returns an instance of ManagedArray from shared pool. + /// + /// + /// The resources must be released by calling FreePooled() instead of Free()-method. + /// + internal static ManagedArray AllocatePooledArray(T* ptr, int length) where T : unmanaged + { + ManagedArray managedArray = ManagedArrayPool.Get(); + managedArray.Allocate(ptr, length); + return managedArray; + } + + internal ManagedArray(Array arr) => WrapArray(arr); + + internal void WrapArray(Array arr) + { + pinnedArrayHandle = GCHandle.Alloc(arr, GCHandleType.Pinned); + unmanagedData = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0); length = arr.Length; elementSize = Marshal.SizeOf(arr.GetType().GetElementType()); } - private ManagedArray(void* ptr, int length, int elementSize) + internal void Allocate(T* ptr, int length) where T : unmanaged { - array = null; - this.unmanagedData = new IntPtr(ptr); + unmanagedData = new IntPtr(ptr); + this.length = length; + elementSize = Unsafe.SizeOf(); + } + + internal void Allocate(IntPtr ptr, int length, int elementSize) + { + unmanagedData = ptr; this.length = length; this.elementSize = elementSize; } + private ManagedArray() { } + + private ManagedArray(void* ptr, int length, int elementSize) => Allocate(new IntPtr(ptr), length, elementSize); + ~ManagedArray() { - if (unmanagedData != IntPtr.Zero || array != null) - Release(); + if (unmanagedData != IntPtr.Zero) + Free(); } - internal void Release() + internal void Free() { GC.SuppressFinalize(this); if (pinnedArrayHandle.IsAllocated) + { pinnedArrayHandle.Free(); - array = null; + unmanagedData = IntPtr.Zero; + } if (unmanagedData != IntPtr.Zero) { Marshal.FreeHGlobal(unmanagedData); @@ -241,41 +286,63 @@ namespace FlaxEngine } } - internal IntPtr PointerToPinnedArray + internal void FreePooled() { - get - { - if (array != null) - { - // Pin the array when it's accessed for the first time - if (!pinnedArrayHandle.IsAllocated) - pinnedArrayHandle = GCHandle.Alloc(array); - return Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); - } - else - return unmanagedData; - } + Free(); + ManagedArrayPool.Put(this); } - internal void SetValue(IntPtr value, int index) - { - if (array != null) - array.SetValue(value, index); - else - GetSpan()[index] = value; - } + internal IntPtr GetPointer => unmanagedData; internal int Length => length; internal int ElementSize => elementSize; - internal Span GetSpan() where T : struct => array != null ? new Span((T[]) array) : new Span(unmanagedData.ToPointer(), length); + internal Span GetSpan() where T : struct => new Span(unmanagedData.ToPointer(), length); - internal T[] GetArray() where T : struct => array != null ? (T[])array : new Span(unmanagedData.ToPointer(), length).ToArray(); + internal T[] GetArray() where T : struct => new Span(unmanagedData.ToPointer(), length).ToArray(); - internal static ManagedArray Get(Array arr) => new ManagedArray(arr); + /// + /// Provides a pool of pre-allocated ManagedArray that can be re-used. + /// + private static class ManagedArrayPool + { + private static List> pool = new List>(); - internal static ManagedArray Get(T* ptr, int length) where T : unmanaged => new ManagedArray(ptr, length, Unsafe.SizeOf()); + internal static ManagedArray Get() + { + for (int i = 0; i < pool.Count; i++) + { + if (!pool[i].Item1) + { + var tuple = pool[i]; + tuple.Item1 = true; + pool[i] = tuple; + return tuple.Item2; + } + } + + var newTuple = (true, new ManagedArray()); + pool.Add(newTuple); + return newTuple.Item2; + } + + internal static void Put(ManagedArray obj) + { + for (int i = 0; i < pool.Count; i++) + { + if (pool[i].Item2 == obj) + { + var tuple = pool[i]; + tuple.Item1 = false; + pool[i] = tuple; + return; + } + } + + throw new Exception("Tried to free non-pooled ManagedArray as pooled ManagedArray"); + } + } } internal static class ManagedString @@ -446,7 +513,7 @@ namespace FlaxEngine public void FromManaged(Array managed) { if (managed != null) - managedArray = ManagedArray.Get(managed); + managedArray = ManagedArray.WrapPooledArray(managed); } public IntPtr ToUnmanaged() @@ -462,8 +529,8 @@ namespace FlaxEngine { if (managedArray != null) { + managedArray.FreePooled(); handle.Free(); - managedArray.Release(); } } } @@ -567,7 +634,7 @@ namespace FlaxEngine return; GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); - (Unsafe.As(handle.Target)).Release(); + (Unsafe.As(handle.Target)).Free(); handle.Free(); } @@ -593,7 +660,7 @@ namespace FlaxEngine numElements = managed.Length; - ManagedArray managedArray = ManagedArray.Get((TUnmanagedElement*)Marshal.AllocHGlobal(sizeof(TUnmanagedElement) * managed.Length), managed.Length); + ManagedArray managedArray = ManagedArray.AllocatePooledArray((TUnmanagedElement*)Marshal.AllocHGlobal(sizeof(TUnmanagedElement) * managed.Length), managed.Length); var ptr = GCHandle.ToIntPtr(GCHandle.Alloc(managedArray)); return (TUnmanagedElement*)ptr; @@ -616,7 +683,7 @@ namespace FlaxEngine return; GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); - (Unsafe.As(handle.Target)).Release(); + (Unsafe.As(handle.Target)).FreePooled(); handle.Free(); } } @@ -633,7 +700,8 @@ namespace FlaxEngine return; managedArray = managed; - unmanagedArray = ManagedArray.Get((TUnmanagedElement*)Marshal.AllocHGlobal(sizeof(TUnmanagedElement) * managed.Length), managed.Length); + + unmanagedArray = ManagedArray.AllocatePooledArray((TUnmanagedElement*)Marshal.AllocHGlobal(sizeof(TUnmanagedElement) * managed.Length), managed.Length); handle = GCHandle.Alloc(unmanagedArray); } @@ -670,7 +738,7 @@ namespace FlaxEngine public void Free() { - unmanagedArray.Release(); + unmanagedArray.FreePooled(); handle.Free(); } } @@ -685,7 +753,7 @@ namespace FlaxEngine numElements = managed.Length; - ManagedArray managedArray = ManagedArray.Get((TUnmanagedElement*)Marshal.AllocHGlobal(sizeof(TUnmanagedElement) * managed.Length), managed.Length); + ManagedArray managedArray = ManagedArray.AllocatePooledArray((TUnmanagedElement*)Marshal.AllocHGlobal(sizeof(TUnmanagedElement) * managed.Length), managed.Length); IntPtr handle = GCHandle.ToIntPtr(GCHandle.Alloc(managedArray)); return (TUnmanagedElement*)handle; @@ -721,7 +789,7 @@ namespace FlaxEngine return; GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); - Unsafe.As(handle.Target).Release(); + Unsafe.As(handle.Target).FreePooled(); handle.Free(); } } @@ -1833,10 +1901,19 @@ namespace FlaxEngine internal static IntPtr NewArray(IntPtr typeHandle, long size) { Type type = Unsafe.As(GCHandle.FromIntPtr(typeHandle).Target); - - Array arr = ArrayFactory.CreateArray(type, size); - ManagedArray managedArray = ManagedArray.Get(arr); - return GCHandle.ToIntPtr(GCHandle.Alloc(managedArray/*, GCHandleType.Weak*/)); + + Type marshalledType = ArrayFactory.GetMarshalledType(type); + if (marshalledType.IsValueType) + { + ManagedArray managedArray = ManagedArray.AllocateNewArray(Marshal.AllocHGlobal(Marshal.SizeOf(marshalledType) * (int)size), (int)size, Marshal.SizeOf(marshalledType)); + return GCHandle.ToIntPtr(GCHandle.Alloc(managedArray/*, GCHandleType.Weak*/)); + } + else + { + Array arr = ArrayFactory.CreateArray(type, size); + ManagedArray managedArray = ManagedArray.WrapNewArray(arr); + return GCHandle.ToIntPtr(GCHandle.Alloc(managedArray/*, GCHandleType.Weak*/)); + } } [UnmanagedCallersOnly] @@ -1847,7 +1924,7 @@ namespace FlaxEngine return IntPtr.Zero; Assert.IsTrue(index >= 0 && index < managedArray.Length); - return IntPtr.Add(managedArray.PointerToPinnedArray, size * index); + return IntPtr.Add(managedArray.GetPointer, size * index); } [UnmanagedCallersOnly] @@ -2037,7 +2114,7 @@ namespace FlaxEngine else if (methodBlob.method.ReturnType == typeof(Type)) return GCHandle.ToIntPtr(GetOrAddTypeGCHandle(Unsafe.As(returnObject))); else if (methodBlob.method.ReturnType == typeof(object[])) - return GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(ManagedArrayToGCHandleArray(Unsafe.As(returnObject))), GCHandleType.Weak)); + return GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As(returnObject))), GCHandleType.Weak)); else return GCHandle.ToIntPtr(GCHandle.Alloc(returnObject, GCHandleType.Weak)); } @@ -2087,8 +2164,8 @@ namespace FlaxEngine internal static void SetArrayValueReference(IntPtr arrayHandle, IntPtr elementPtr, IntPtr valueHandle) { ManagedArray managedArray = Unsafe.As(GCHandle.FromIntPtr(arrayHandle).Target); - int index = (int)(elementPtr.ToInt64() - managedArray.PointerToPinnedArray.ToInt64()) / managedArray.ElementSize; - managedArray.SetValue(valueHandle, index); + int index = (int)(elementPtr.ToInt64() - managedArray.GetPointer.ToInt64()) / managedArray.ElementSize; + managedArray.GetSpan()[index] = valueHandle; } [UnmanagedCallersOnly] @@ -2647,7 +2724,7 @@ namespace FlaxEngine else if (method.ReturnType == typeof(Type)) return GCHandle.ToIntPtr(GetOrAddTypeGCHandle(Unsafe.As(returnObject))); else if (method.ReturnType == typeof(object[])) - return GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(ManagedArrayToGCHandleArray(Unsafe.As(returnObject))), GCHandleType.Weak)); + return GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As(returnObject))), GCHandleType.Weak)); else return GCHandle.ToIntPtr(GCHandle.Alloc(returnObject, GCHandleType.Weak)); } diff --git a/Source/Engine/Engine/NativeInterop_Invoker.cs b/Source/Engine/Engine/NativeInterop_Invoker.cs index 25be65bc1..9567a6fc4 100644 --- a/Source/Engine/Engine/NativeInterop_Invoker.cs +++ b/Source/Engine/Engine/NativeInterop_Invoker.cs @@ -59,7 +59,7 @@ namespace FlaxEngine else if (typeof(TRet) == typeof(Type)) return returnValue != null ? GCHandle.ToIntPtr(GetOrAddTypeGCHandle(Unsafe.As(returnValue))) : IntPtr.Zero; else if (typeof(TRet) == typeof(object[])) - return returnValue != null ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(ManagedArrayToGCHandleArray(Unsafe.As(returnValue))), GCHandleType.Weak)) : IntPtr.Zero; + return returnValue != null ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As(returnValue))), GCHandleType.Weak)) : IntPtr.Zero; else return returnValue != null ? GCHandle.ToIntPtr(GCHandle.Alloc(returnValue, GCHandleType.Weak)) : IntPtr.Zero; } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 9103f82a4..890f0524b 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1499,25 +1499,25 @@ namespace Flax.Build.Bindings string originalElementTypeMarshaller = originalElementType + "Marshaller"; string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.NativeArrayToManagedArray<{originalElementType}, {internalElementType}>(((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).GetSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null"); - toNativeContent.Append($"GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(managed.{fieldInfo.Name}), GCHandleType.Weak))"); - freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).GetSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); - freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).GetSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + toNativeContent.Append($"GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak))"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).GetSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).GetSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}"); } else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.{fieldInfo.Name})), GCHandleType.Weak)) : IntPtr.Zero"); - freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = ((ManagedArray)handle.Target).GetSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); - freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = ((ManagedArray)handle.Target).GetSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.{fieldInfo.Name})), GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = ((ManagedArray)handle.Target).GetSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = ((ManagedArray)handle.Target).GetSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}"); } else { // Blittable array elements toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).GetArray<{originalElementType}>() : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(managed.{fieldInfo.Name}), GCHandleType.Weak)) : IntPtr.Zero"); - freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); - freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Free(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Free(); handle.Free(); }}"); } } else if (fieldInfo.Type.Type == "Version")