From d64209519078b4f610e295351c444a6622b14782 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Mon, 31 Jul 2023 20:21:33 +0200 Subject: [PATCH 1/7] Added a max memory usage monitor --- .../Application/Core/ChallengeController.cs | 8 +- .../Framework/Application/Core/Settings.cs | 1 + .../Application/Helpers/ObjectSizeHelper.cs | 73 +++++++++++++++++++ .../Application/UI/BotBrainCapacityUI.cs | 66 ++++++++++++----- 4 files changed, 130 insertions(+), 18 deletions(-) create mode 100644 Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs diff --git a/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs b/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs index 4d6822875..1c64c9e7a 100644 --- a/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs +++ b/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs @@ -55,6 +55,7 @@ public enum PlayerType readonly int tokenCount; readonly int debugTokenCount; readonly StringBuilder pgns; + long maxMemoryUsed = 0; public ChallengeController() { @@ -233,6 +234,10 @@ void OnMoveChosen(Move chosenMove) moveToPlay = chosenMove; isWaitingToPlayMove = true; playMoveTime = lastMoveMadeTime + MinMoveDelay; + if (PlayerToMove.Bot is MyBot) + { + maxMemoryUsed = Math.Max(ObjectSizeHelper.GetSize(PlayerToMove.Bot), maxMemoryUsed); + } } else { @@ -389,7 +394,8 @@ public void Draw() public void DrawOverlay() { - BotBrainCapacityUI.Draw(tokenCount, debugTokenCount, MaxTokenCount); + BotBrainCapacityUI.DrawTokenUsage(tokenCount, debugTokenCount, MaxTokenCount); + BotBrainCapacityUI.DrawMemoryUsage(maxMemoryUsed, MaxMemoryUsage); MenuUI.DrawButtons(this); MatchStatsUI.DrawMatchStats(this); } diff --git a/Chess-Challenge/src/Framework/Application/Core/Settings.cs b/Chess-Challenge/src/Framework/Application/Core/Settings.cs index 37f694afa..947def165 100644 --- a/Chess-Challenge/src/Framework/Application/Core/Settings.cs +++ b/Chess-Challenge/src/Framework/Application/Core/Settings.cs @@ -20,6 +20,7 @@ public static class Settings // Other settings public const int MaxTokenCount = 1024; public const LogType MessagesToLog = LogType.All; + public const int MaxMemoryUsage = 256 * 1024 * 1024; public enum LogType { diff --git a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs new file mode 100644 index 000000000..ad394d9af --- /dev/null +++ b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace ChessChallenge.Application +{ + public static class ObjectSizeHelper + { + static readonly long ObjectSize = IntPtr.Size == 8 ? 24 : 12; + static readonly long PointerSize = IntPtr.Size; + + public static long GetSize(object? obj) + { + if (obj is null) + { + return 0; + } + + var type = obj.GetType(); + + // ignore references to the API board + if (typeof(API.Board) == type) + { + return 0; + } + + if (type.IsEnum) + { + var underlyingType = Enum.GetUnderlyingType(type); + return Marshal.SizeOf(underlyingType); + } + + if (type.IsValueType) + { + // Marshal.SizeOf() does not work for structs with reference types + var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + return fieldInfos.Any(x => x.FieldType.IsClass) + ? GetFieldsMemorySize(obj, fieldInfos) + : Marshal.SizeOf(obj); + } + + if (type == typeof(string)) + { + var str = (string)obj; + return str.Length * 2 + 6 + ObjectSize; + } + + if (obj is IEnumerable enumerable) + { + return ObjectSize + enumerable.Cast().Sum(item => GetObjectSize(item, item?.GetType() ?? typeof(object))); + } + + if (type.IsClass) + { + return ObjectSize + GetFieldsMemorySize(obj, type, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + + throw new ArgumentException($"Unknown type {type.Name}", nameof(obj)); + } + + private static long GetFieldsMemorySize(object obj, Type type, BindingFlags bindingFlags) + => GetFieldsMemorySize(obj, type.GetFields(bindingFlags)); + + private static long GetFieldsMemorySize(object obj, IEnumerable fieldInfos) + => fieldInfos.Sum(x => GetObjectSize(x.GetValue(obj), x.GetType())); + + private static long GetObjectSize(object? obj, Type type) + => GetSize(obj) + (type.IsClass ? PointerSize : 0); + } +} diff --git a/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs b/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs index 50f25af75..114a5dfff 100644 --- a/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs +++ b/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs @@ -1,4 +1,5 @@ -using Raylib_cs; +using System; +using Raylib_cs; namespace ChessChallenge.Application { @@ -10,19 +11,45 @@ public static class BotBrainCapacityUI static readonly Color red = new(219, 9, 9, 255); static readonly Color background = new Color(40, 40, 40, 255); - public static void Draw(int totalTokenCount, int debugTokenCount, int tokenLimit) + public static void DrawTokenUsage(int totalTokenCount, int debugTokenCount, int tokenLimit) { int activeTokenCount = totalTokenCount - debugTokenCount; - int screenWidth = Raylib.GetScreenWidth(); + int startPos = Raylib.GetScreenWidth() / 2; + double t = (double)activeTokenCount / tokenLimit; + string text = $"Bot Brain Capacity: {activeTokenCount}/{tokenLimit}"; + if (activeTokenCount > tokenLimit) + { + text += " [LIMIT EXCEEDED]"; + } + else if (debugTokenCount != 0) + { + text += $" ({totalTokenCount} with Debugs included)"; + } + Draw(text, t, startPos); + } + + public static void DrawMemoryUsage(long memorySize, long memoryLimit) + { + int startPos = 0; + double t = (double)memorySize / memoryLimit; + string text = $"Bot Memory Capacity: {FriendlySize(memorySize)} / {FriendlySize(memoryLimit)}"; + if (memorySize > memoryLimit) + { + text += " [LIMIT EXCEEDED]"; + } + Draw(text, t, startPos); + } + + static void Draw(string text, double t, int startPos) + { + int barWidth = Raylib.GetScreenWidth() / 2; int screenHeight = Raylib.GetScreenHeight(); - int height = UIHelper.ScaleInt(48); + int barHeight = UIHelper.ScaleInt(48); int fontSize = UIHelper.ScaleInt(35); // Bg - Raylib.DrawRectangle(0, screenHeight - height, screenWidth, height, background); + Raylib.DrawRectangle(startPos, screenHeight - barHeight, startPos + barWidth, barHeight, background); // Bar - double t = (double)activeTokenCount / tokenLimit; - Color col; if (t <= 0.7) col = green; @@ -32,19 +59,24 @@ public static void Draw(int totalTokenCount, int debugTokenCount, int tokenLimit col = orange; else col = red; - Raylib.DrawRectangle(0, screenHeight - height, (int)(screenWidth * t), height, col); + + Raylib.DrawRectangle(startPos, screenHeight - barHeight, startPos + (int)(barWidth * Math.Min(t, 1)), barHeight, col); - var textPos = new System.Numerics.Vector2(screenWidth / 2, screenHeight - height / 2); - string text = $"Bot Brain Capacity: {activeTokenCount}/{tokenLimit}"; - if (activeTokenCount > tokenLimit) - { - text += " [LIMIT EXCEEDED]"; - } - else if (debugTokenCount != 0) + var textPos = new System.Numerics.Vector2(startPos + barWidth / 2, screenHeight - barHeight / 2); + UIHelper.DrawText(text, textPos, fontSize, 1, Color.WHITE, UIHelper.AlignH.Centre); + } + + static readonly string[] sizes = { "B", "KB", "MB" }; + static string FriendlySize(long size) + { + double friendlySize = size; + int order = 0; + while (friendlySize >= 1024 && order < sizes.Length - 1) { - text += $" ({totalTokenCount} with Debugs included)"; + order++; + friendlySize /= 1024; } - UIHelper.DrawText(text, textPos, fontSize, 1, Color.WHITE, UIHelper.AlignH.Centre); + return $"{friendlySize:0.##} {sizes[order]}"; } } } \ No newline at end of file From cc7f4f11b14d99323133ca741eeedce2f64b1390 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Mon, 31 Jul 2023 20:53:36 +0200 Subject: [PATCH 2/7] ignore circular references --- .../Application/Helpers/ObjectSizeHelper.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs index ad394d9af..deddfb460 100644 --- a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs +++ b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs @@ -12,7 +12,9 @@ public static class ObjectSizeHelper static readonly long ObjectSize = IntPtr.Size == 8 ? 24 : 12; static readonly long PointerSize = IntPtr.Size; - public static long GetSize(object? obj) + public static long GetSize(object? obj) => GetSize(obj, new HashSet()); + + static long GetSize(object? obj, HashSet seenObjects) { if (obj is null) { @@ -38,7 +40,7 @@ public static long GetSize(object? obj) // Marshal.SizeOf() does not work for structs with reference types var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); return fieldInfos.Any(x => x.FieldType.IsClass) - ? GetFieldsMemorySize(obj, fieldInfos) + ? GetFieldsMemorySize(obj, fieldInfos, seenObjects) : Marshal.SizeOf(obj); } @@ -50,24 +52,38 @@ public static long GetSize(object? obj) if (obj is IEnumerable enumerable) { - return ObjectSize + enumerable.Cast().Sum(item => GetObjectSize(item, item?.GetType() ?? typeof(object))); + return ObjectSize + enumerable.Cast().Sum(item => GetObjectSize(item, item?.GetType() ?? typeof(object), seenObjects)); } if (type.IsClass) { - return ObjectSize + GetFieldsMemorySize(obj, type, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + return ObjectSize + GetFieldsMemorySize(obj, type, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, seenObjects); } throw new ArgumentException($"Unknown type {type.Name}", nameof(obj)); } - private static long GetFieldsMemorySize(object obj, Type type, BindingFlags bindingFlags) - => GetFieldsMemorySize(obj, type.GetFields(bindingFlags)); + private static long GetFieldsMemorySize(object obj, Type type, BindingFlags bindingFlags, HashSet seenObjects) + => GetFieldsMemorySize(obj, type.GetFields(bindingFlags), seenObjects); - private static long GetFieldsMemorySize(object obj, IEnumerable fieldInfos) - => fieldInfos.Sum(x => GetObjectSize(x.GetValue(obj), x.GetType())); + private static long GetFieldsMemorySize(object obj, IEnumerable fieldInfos, HashSet seenObjects) + => fieldInfos.Sum(x => GetObjectSize(x.GetValue(obj), x.GetType(), seenObjects)); - private static long GetObjectSize(object? obj, Type type) - => GetSize(obj) + (type.IsClass ? PointerSize : 0); + private static long GetObjectSize(object? obj, Type type, HashSet seenObjects) + { + if (type.IsClass && obj is not null && seenObjects.Contains(obj)) + { + return 0; + } + + var size = GetSize(obj) + (type.IsClass ? PointerSize : 0); + + if (type.IsClass && obj is not null) + { + seenObjects.Add(obj); + } + + return size; + } } } From 9bd8517919feb4b155248f9a68f39cdb0f1bfbe4 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Tue, 1 Aug 2023 00:34:31 +0200 Subject: [PATCH 3/7] use Unsafe.SizeOf for structs --- .../Application/Helpers/ObjectSizeHelper.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs index deddfb460..e41074310 100644 --- a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs +++ b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs @@ -38,10 +38,11 @@ static long GetSize(object? obj, HashSet seenObjects) if (type.IsValueType) { // Marshal.SizeOf() does not work for structs with reference types + // Marshal.SizeOf() also does not work with generics, so we need to use Unsafe.SizeOf() var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); return fieldInfos.Any(x => x.FieldType.IsClass) ? GetFieldsMemorySize(obj, fieldInfos, seenObjects) - : Marshal.SizeOf(obj); + : GetStructSize(type); } if (type == typeof(string)) @@ -63,11 +64,26 @@ static long GetSize(object? obj, HashSet seenObjects) throw new ArgumentException($"Unknown type {type.Name}", nameof(obj)); } + static readonly Dictionary sizeOfTypeCache = new(); + private static long GetStructSize(Type type) + { + if (!sizeOfTypeCache.TryGetValue(type, out var sizeOfType)) + { + var genericSizeOf = typeof(System.Runtime.CompilerServices.Unsafe) + .GetMethod(nameof(System.Runtime.CompilerServices.Unsafe.SizeOf)) + .MakeGenericMethod(type); + + sizeOfTypeCache[type] = sizeOfType = (int)genericSizeOf.Invoke(null, null)!; + } + + return sizeOfType; + } + private static long GetFieldsMemorySize(object obj, Type type, BindingFlags bindingFlags, HashSet seenObjects) => GetFieldsMemorySize(obj, type.GetFields(bindingFlags), seenObjects); private static long GetFieldsMemorySize(object obj, IEnumerable fieldInfos, HashSet seenObjects) - => fieldInfos.Sum(x => GetObjectSize(x.GetValue(obj), x.GetType(), seenObjects)); + => fieldInfos.Sum(x => GetObjectSize(x.GetValue(obj), x.FieldType, seenObjects)); private static long GetObjectSize(object? obj, Type type, HashSet seenObjects) { @@ -76,7 +92,7 @@ private static long GetObjectSize(object? obj, Type type, HashSet seenOb return 0; } - var size = GetSize(obj) + (type.IsClass ? PointerSize : 0); + var size = GetSize(obj, seenObjects) + (type.IsClass ? PointerSize : 0); if (type.IsClass && obj is not null) { From d6320136ea393a841995bb165e577cb751d63443 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Tue, 1 Aug 2023 00:35:58 +0200 Subject: [PATCH 4/7] - log exceptions when memory usage cannot be determined - stop the clock when calculating the memory usage --- .../Application/Core/ChallengeController.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs b/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs index 1c64c9e7a..7f657c0eb 100644 --- a/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs +++ b/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs @@ -236,7 +236,22 @@ void OnMoveChosen(Move chosenMove) playMoveTime = lastMoveMadeTime + MinMoveDelay; if (PlayerToMove.Bot is MyBot) { - maxMemoryUsed = Math.Max(ObjectSizeHelper.GetSize(PlayerToMove.Bot), maxMemoryUsed); + isPlaying = false; + try + { + maxMemoryUsed = Math.Max(ObjectSizeHelper.GetSize(PlayerToMove.Bot), maxMemoryUsed); + } + catch (Exception e) + { + Log("An error occurred while determining used memory size.\n" + e.ToString(), true, + ConsoleColor.Red); + hasBotTaskException = true; + botExInfo = ExceptionDispatchInfo.Capture(e); + } + finally + { + isPlaying = true; + } } } else From 14cee450ba2ba1c4fdb9915145e90538922a65a9 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Tue, 1 Aug 2023 00:54:58 +0200 Subject: [PATCH 5/7] ignore API.Timer --- .../src/Framework/Application/Helpers/ObjectSizeHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs index e41074310..a383a8d46 100644 --- a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs +++ b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs @@ -23,8 +23,8 @@ static long GetSize(object? obj, HashSet seenObjects) var type = obj.GetType(); - // ignore references to the API board - if (typeof(API.Board) == type) + // ignore references to the API board and the API Timer + if (typeof(API.Board) == type || typeof(API.Timer) == type) { return 0; } From d6561bb7528bf4d0e534504e6d049df0c1b70b43 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Wed, 2 Aug 2023 21:06:14 +0200 Subject: [PATCH 6/7] added Settings.MonitorMemoryUsage and optimized ObjectSizeHelper (somewhat) --- .../Application/Core/ChallengeController.cs | 2 +- .../Framework/Application/Core/Settings.cs | 1 + .../Application/Helpers/ObjectSizeHelper.cs | 79 +++++++++++++++---- .../Application/UI/BotBrainCapacityUI.cs | 4 + 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs b/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs index 7f657c0eb..92acb7d23 100644 --- a/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs +++ b/Chess-Challenge/src/Framework/Application/Core/ChallengeController.cs @@ -234,7 +234,7 @@ void OnMoveChosen(Move chosenMove) moveToPlay = chosenMove; isWaitingToPlayMove = true; playMoveTime = lastMoveMadeTime + MinMoveDelay; - if (PlayerToMove.Bot is MyBot) + if (MonitorMemoryUsage && PlayerToMove.Bot is MyBot) { isPlaying = false; try diff --git a/Chess-Challenge/src/Framework/Application/Core/Settings.cs b/Chess-Challenge/src/Framework/Application/Core/Settings.cs index 947def165..6f525a177 100644 --- a/Chess-Challenge/src/Framework/Application/Core/Settings.cs +++ b/Chess-Challenge/src/Framework/Application/Core/Settings.cs @@ -21,6 +21,7 @@ public static class Settings public const int MaxTokenCount = 1024; public const LogType MessagesToLog = LogType.All; public const int MaxMemoryUsage = 256 * 1024 * 1024; + public const bool MonitorMemoryUsage = false; public enum LogType { diff --git a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs index a383a8d46..f17a59ec6 100644 --- a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs +++ b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs @@ -31,16 +31,28 @@ static long GetSize(object? obj, HashSet seenObjects) if (type.IsEnum) { - var underlyingType = Enum.GetUnderlyingType(type); - return Marshal.SizeOf(underlyingType); + if (!sizeOfTypeCache.TryGetValue(type, out var sizeOfType)) + { + var underlyingType = Enum.GetUnderlyingType(type); + sizeOfTypeCache[type] = sizeOfType = Marshal.SizeOf(underlyingType); + } + + return sizeOfType; } if (type.IsValueType) { // Marshal.SizeOf() does not work for structs with reference types // Marshal.SizeOf() also does not work with generics, so we need to use Unsafe.SizeOf() - var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); - return fieldInfos.Any(x => x.FieldType.IsClass) + if (!fieldInfoCache.TryGetValue(type, out var fieldInfos)) + { + fieldInfoCache[type] = fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + if (!typeContainsClassCache.TryGetValue(type, out var typeContainsClass)) + { + typeContainsClassCache[type] = typeContainsClass = fieldInfos.Any(x => TypeIsClass(x.FieldType)); + } + return typeContainsClass ? GetFieldsMemorySize(obj, fieldInfos, seenObjects) : GetStructSize(type); } @@ -50,19 +62,36 @@ static long GetSize(object? obj, HashSet seenObjects) var str = (string)obj; return str.Length * 2 + 6 + ObjectSize; } - - if (obj is IEnumerable enumerable) + + if (obj is IList collection) { - return ObjectSize + enumerable.Cast().Sum(item => GetObjectSize(item, item?.GetType() ?? typeof(object), seenObjects)); + var totalSize = ObjectSize; + + for (var index = 0; index < collection.Count; index++) + { + var item = collection[index]; + totalSize += GetObjectSize(item, item?.GetType() ?? typeof(object), seenObjects); + } + + return totalSize; } - if (type.IsClass) + if (TypeIsClass(type)) { - return ObjectSize + GetFieldsMemorySize(obj, type, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, seenObjects); + if (!fieldInfoCache.TryGetValue(type, out var fieldInfos)) + { + fieldInfoCache[type] = fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + + return ObjectSize + GetFieldsMemorySize(obj, fieldInfos, seenObjects); } throw new ArgumentException($"Unknown type {type.Name}", nameof(obj)); } + + static readonly Dictionary fieldInfoCache = new(); + static readonly Dictionary typeContainsClassCache = new(); + static readonly Dictionary typeIsClassCache = new(); static readonly Dictionary sizeOfTypeCache = new(); private static long GetStructSize(Type type) @@ -79,22 +108,42 @@ private static long GetStructSize(Type type) return sizeOfType; } - private static long GetFieldsMemorySize(object obj, Type type, BindingFlags bindingFlags, HashSet seenObjects) - => GetFieldsMemorySize(obj, type.GetFields(bindingFlags), seenObjects); + private static long GetFieldsMemorySize(object obj, FieldInfo[] fieldInfos, HashSet seenObjects) + { + var totalSize = 0L; - private static long GetFieldsMemorySize(object obj, IEnumerable fieldInfos, HashSet seenObjects) - => fieldInfos.Sum(x => GetObjectSize(x.GetValue(obj), x.FieldType, seenObjects)); + for (var index = 0; index < fieldInfos.Length; index++) + { + var fieldInfo = fieldInfos[index]; + var fieldValue = fieldInfo.GetValue(obj); + totalSize += GetObjectSize(fieldValue, fieldInfo.FieldType, seenObjects); + } + + return totalSize; + } + + private static bool TypeIsClass(Type type) + { + if (!typeIsClassCache.TryGetValue(type, out var typeIsClass)) + { + typeIsClassCache[type] = typeIsClass = type.IsClass; + } + + return typeIsClass; + } private static long GetObjectSize(object? obj, Type type, HashSet seenObjects) { - if (type.IsClass && obj is not null && seenObjects.Contains(obj)) + var typeIsClass = TypeIsClass(type); + + if (obj is not null && typeIsClass && seenObjects.Contains(obj)) { return 0; } var size = GetSize(obj, seenObjects) + (type.IsClass ? PointerSize : 0); - if (type.IsClass && obj is not null) + if (obj is not null && typeIsClass) { seenObjects.Add(obj); } diff --git a/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs b/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs index 114a5dfff..44ed7e5e1 100644 --- a/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs +++ b/Chess-Challenge/src/Framework/Application/UI/BotBrainCapacityUI.cs @@ -69,6 +69,10 @@ static void Draw(string text, double t, int startPos) static readonly string[] sizes = { "B", "KB", "MB" }; static string FriendlySize(long size) { + if (size == 0) + { + return "--"; + } double friendlySize = size; int order = 0; while (friendlySize >= 1024 && order < sizes.Length - 1) From 94002f90855c27ebcf278e99700c4c88b8a88d00 Mon Sep 17 00:00:00 2001 From: Ryan Heath Date: Wed, 2 Aug 2023 21:50:57 +0200 Subject: [PATCH 7/7] more ObjectSizeHelper optimizations --- .../src/Framework/Application/Core/Settings.cs | 2 +- .../src/Framework/Application/Helpers/ObjectSizeHelper.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Chess-Challenge/src/Framework/Application/Core/Settings.cs b/Chess-Challenge/src/Framework/Application/Core/Settings.cs index 6f525a177..d4f34a36f 100644 --- a/Chess-Challenge/src/Framework/Application/Core/Settings.cs +++ b/Chess-Challenge/src/Framework/Application/Core/Settings.cs @@ -21,7 +21,7 @@ public static class Settings public const int MaxTokenCount = 1024; public const LogType MessagesToLog = LogType.All; public const int MaxMemoryUsage = 256 * 1024 * 1024; - public const bool MonitorMemoryUsage = false; + public const bool MonitorMemoryUsage = true; public enum LogType { diff --git a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs index f17a59ec6..cdaa6991a 100644 --- a/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs +++ b/Chess-Challenge/src/Framework/Application/Helpers/ObjectSizeHelper.cs @@ -67,6 +67,14 @@ static long GetSize(object? obj, HashSet seenObjects) { var totalSize = ObjectSize; + // structs are counted easy + var typeZero = collection.Count > 0 ? collection[0]?.GetType() : null; + if (typeZero is not null && sizeOfTypeCache.TryGetValue(typeZero, out var sizeOfType)) + { + totalSize += collection.Count * sizeOfType; + return totalSize; + } + for (var index = 0; index < collection.Count; index++) { var item = collection[index];