From dfca248ebd6b671d6ca4ffa74ab7d972381dda6d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 31 Mar 2023 14:56:57 +0200 Subject: [PATCH] Optimize C# `IsCollectible` to be used in Editor-only builds --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 24 ++++++++++++++----- Source/Engine/Engine/NativeInterop.cs | 24 ++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 2a756a189..4b4cf692a 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -287,10 +287,14 @@ namespace FlaxEngine.Interop FieldHolder fieldHolder = new FieldHolder(fields[i], type); ManagedHandle fieldHandle = ManagedHandle.Alloc(fieldHolder); +#if FLAX_EDITOR if (type.IsCollectible) fieldHandleCacheCollectible.Add(fieldHandle); else +#endif + { fieldHandleCache.Add(fieldHandle); + } NativeFieldDefinitions classField = new NativeFieldDefinitions() { @@ -721,10 +725,14 @@ namespace FlaxEngine.Interop IntPtr functionPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate); // Keep a reference to the delegate to prevent it from being garbage collected +#if FLAX_EDITOR if (methodHolder.method.IsCollectible) cachedDelegatesCollectible[functionPtr] = methodDelegate; else +#endif + { cachedDelegates[functionPtr] = methodDelegate; + } return functionPtr; } @@ -831,21 +839,21 @@ namespace FlaxEngine.Interop AssemblyLocations.Remove(assembly.FullName); // Clear all caches which might hold references to closing assembly - cachedDelegatesCollectible.Clear(); typeCache.Clear(); - // Release all GCHandles in collectible ALC + // Release all references in collectible ALC +#if FLAX_EDITOR + cachedDelegatesCollectible.Clear(); foreach (var pair in typeHandleCacheCollectible) pair.Value.Free(); typeHandleCacheCollectible.Clear(); - foreach (var handle in methodHandlesCollectible) handle.Free(); methodHandlesCollectible.Clear(); - foreach (var handle in fieldHandleCacheCollectible) handle.Free(); fieldHandleCacheCollectible.Clear(); +#endif foreach (var pair in classAttributesCacheCollectible) pair.Value.Free(); @@ -869,8 +877,12 @@ namespace FlaxEngine.Interop while (unloading) System.Threading.Thread.Sleep(1); - // TODO: benchmark collectible setting performance, maybe enable it only in editor builds? - scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", true); +#if FLAX_EDITOR + var isCollectible = true; +#else + var isCollectible = false; +#endif + scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible); DelegateHelpers.InitMethods(); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 418f2e13b..dc4951f50 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -32,13 +32,15 @@ namespace FlaxEngine.Interop private static IntPtr boolFalsePtr = ManagedHandle.ToIntPtr(ManagedHandle.Alloc((int)0, GCHandleType.Pinned)); private static List methodHandles = new(); - private static List methodHandlesCollectible = new(); private static ConcurrentDictionary cachedDelegates = new(); - private static ConcurrentDictionary cachedDelegatesCollectible = new(); private static Dictionary typeHandleCache = new(); - private static Dictionary typeHandleCacheCollectible = new(); private static List fieldHandleCache = new(); +#if FLAX_EDITOR + private static List methodHandlesCollectible = new(); + private static ConcurrentDictionary cachedDelegatesCollectible = new(); + private static Dictionary typeHandleCacheCollectible = new(); private static List fieldHandleCacheCollectible = new(); +#endif private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new(); @@ -840,10 +842,14 @@ namespace FlaxEngine.Interop { MethodHolder methodHolder = new MethodHolder(method); ManagedHandle handle = ManagedHandle.Alloc(methodHolder); +#if FLAX_EDITOR if (methodHolder.parameterTypes.Any(x => x.IsCollectible) || method.IsCollectible) methodHandlesCollectible.Add(handle); else +#endif + { methodHandles.Add(handle); + } return handle; } @@ -975,14 +981,20 @@ namespace FlaxEngine.Interop { if (typeHandleCache.TryGetValue(type, out ManagedHandle handle)) return handle; +#if FLAX_EDITOR if (typeHandleCacheCollectible.TryGetValue(type, out handle)) return handle; +#endif handle = ManagedHandle.Alloc(type); +#if FLAX_EDITOR if (type.IsCollectible) // check if generic parameters are also collectible? typeHandleCacheCollectible.Add(type, handle); else +#endif + { typeHandleCache.Add(type, handle); + } return handle; } @@ -990,7 +1002,9 @@ namespace FlaxEngine.Interop private static class DelegateHelpers { private static Func MakeNewCustomDelegateFunc; +#if FLAX_EDITOR private static Func MakeNewCustomDelegateFuncCollectible; +#endif internal static void InitMethods() { @@ -998,6 +1012,7 @@ namespace FlaxEngine.Interop typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); +#if FLAX_EDITOR // Load System.Linq.Expressions assembly to collectible ALC. // The dynamic assembly where delegates are stored is cached in the DelegateHelpers class, so we should // use the DelegateHelpers in collectible ALC to make sure the delegates are also stored in the same ALC. @@ -1013,12 +1028,15 @@ namespace FlaxEngine.Interop using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) }); } +#endif } internal static Type MakeNewCustomDelegate(Type[] parameters) { +#if FLAX_EDITOR if (parameters.Any(x => x.IsCollectible)) return MakeNewCustomDelegateFuncCollectible(parameters); +#endif return MakeNewCustomDelegateFunc(parameters); } }