Created
October 19, 2025 12:34
-
-
Save adammyhre/51aed0c1168ed7fcedf3d6ca6f5c036b to your computer and use it in GitHub Desktop.
Allocation Analyzer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using UnityEngine.Profiling; | |
| // See UnityEngine.TestTools.Constraints.AllocatingGCMemoryConstraint | |
| // and https://maingauche.games/devlog/20230504-counting-allocations-in-unity/ | |
| public class AllocCounter { | |
| UnityEngine.Profiling.Recorder rec; | |
| public AllocCounter() { | |
| rec = Recorder.Get("GC.Alloc"); | |
| rec.enabled = false; | |
| #if !UNITY_WEBGL | |
| rec.FilterToCurrentThread(); | |
| #endif | |
| rec.enabled = true; | |
| } | |
| public int Stop() { | |
| if (rec == null) throw new InvalidOperationException("AllocCounter was not started."); | |
| rec.enabled = false; | |
| #if !UNITY_WEBGL | |
| rec.CollectFromAllThreads(); | |
| #endif | |
| int result = rec.sampleBlockCount; | |
| rec = null; | |
| return result; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using UnityEngine; | |
| using UnityEngine.Profiling; | |
| public class AllocatorDemo : MonoBehaviour { | |
| const int BYTES_TO_MB = 1024 * 1024; | |
| void Start() { | |
| var ac = new AllocCounter(); | |
| long memUsed = System.GC.GetTotalMemory(false); | |
| long monoBytes = Profiler.GetMonoUsedSizeLong(); | |
| byte[] junk = new byte[BYTES_TO_MB]; | |
| var allocCount = ac.Stop(); | |
| long memUsedAfter = GC.GetTotalMemory(false); | |
| long monoBytesAfter = Profiler.GetMonoUsedSizeLong(); | |
| Debug.Log("Allocations during Start: " + allocCount); | |
| Debug.Log("Memory used before: " + memUsed + ", after: " + memUsedAfter + ", diff: " + (memUsedAfter - memUsed)); | |
| Debug.Log("Mono bytes before: " + monoBytes + ", after: " + monoBytesAfter + ", diff: " + (monoBytesAfter - monoBytes)); | |
| } | |
| void Update() { | |
| if (Time.frameCount % 60 == 0) { | |
| byte[] junk = new byte[BYTES_TO_MB * 10]; | |
| Logwin.Log("Allocated 1 MB of junk data.", Time.frameCount); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| // A fixed-size ring buffer that overwrites the oldest entries when full. | |
| public class CircularBuffer<T> { | |
| readonly T[] buffer; | |
| int head, tail; | |
| public int Count { get; private set; } | |
| public int Capacity => buffer.Length; | |
| public CircularBuffer(int size) { | |
| if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); | |
| buffer = new T[size]; | |
| } | |
| public void Enqueue(T item) { | |
| buffer[head] = item; | |
| head = (head + 1) % Capacity; | |
| if (Count == Capacity) tail = (tail + 1) % Capacity; | |
| else Count++; | |
| } | |
| public T Dequeue() { | |
| if (Count == 0) throw new InvalidOperationException("Buffer is empty"); | |
| var item = buffer[tail]; | |
| tail = (tail + 1) % Capacity; | |
| Count--; | |
| return item; | |
| } | |
| // Access elements by logical index (0 = oldest, Count-1 = newest) | |
| public T this[int index] { | |
| get { | |
| if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); | |
| if (Capacity == 0 || buffer == null) throw new InvalidOperationException(); | |
| return buffer[(tail + index) % Capacity]; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine; | |
| using UnityEngine.Profiling; | |
| using System; | |
| using TMPro; | |
| using UnityEngine.UI; | |
| public class MemoryMonitor : MonoBehaviour { | |
| #region Fields | |
| public TextMeshProUGUI allocatedRamText, reservedRamText, monoRamText, gcCountText; | |
| public RawImage memoryGraphImage; | |
| public int historyLength = 300, graphHeight = 100; | |
| public Color32 allocatedColor = new(0, 255, 0, 255), | |
| monoColor = new(0, 150, 255, 255), | |
| reservedColor = new(200, 200, 200, 255), | |
| gcEventColor = new(255, 0, 0, 255); | |
| const float BYTES_TO_MB = 1024f * 1024f; | |
| static readonly Color32 backgroundColor = new(0, 0, 0, 255); | |
| CircularBuffer<long> allocated, reserved, mono, gcAlloc; | |
| CircularBuffer<bool> gcEvents; | |
| Texture2D graphTexture; | |
| Color32[] pixels; | |
| Recorder rec; | |
| int lastGCCount; | |
| long maxReserved; | |
| #endregion | |
| void Start() { | |
| allocated = new(historyLength); | |
| reserved = new(historyLength); | |
| mono = new(historyLength); | |
| gcAlloc = new(historyLength); | |
| gcEvents = new(historyLength); | |
| rec = Recorder.Get("GC.Alloc"); | |
| rec.enabled = false; | |
| rec.FilterToCurrentThread(); | |
| rec.enabled = true; | |
| graphTexture = new Texture2D(historyLength, graphHeight + 1, TextureFormat.RGBA32, false) | |
| { wrapMode = TextureWrapMode.Clamp }; | |
| pixels = new Color32[graphTexture.width * graphTexture.height]; | |
| memoryGraphImage.texture = graphTexture; | |
| } | |
| void Update() { | |
| long allocBytes = Profiler.GetTotalAllocatedMemoryLong(); | |
| long resBytes = Profiler.GetTotalReservedMemoryLong(); | |
| long monoBytes = Profiler.GetMonoUsedSizeLong(); | |
| long gcAllocs = rec.sampleBlockCount; | |
| int gcCount = GC.CollectionCount(0); // Only one generation in Unity at index 0 | |
| bool gcHappened = gcCount != lastGCCount; | |
| lastGCCount = gcCount; | |
| UpdateText(allocatedRamText, allocBytes / BYTES_TO_MB, allocatedColor); | |
| UpdateText(reservedRamText, resBytes / BYTES_TO_MB, reservedColor); | |
| UpdateText(monoRamText, monoBytes / BYTES_TO_MB, monoColor); | |
| UpdateText(gcCountText, gcCount, gcHappened ? Color.red : Color.white); | |
| if (resBytes > maxReserved) maxReserved = resBytes; | |
| allocated.Enqueue(allocBytes); | |
| reserved.Enqueue(resBytes); | |
| mono.Enqueue(monoBytes); | |
| gcAlloc.Enqueue(gcAllocs); | |
| gcEvents.Enqueue(gcHappened); | |
| DrawGraph(); | |
| } | |
| void DrawGraph() { | |
| if (!graphTexture) return; | |
| Array.Fill(pixels, backgroundColor); | |
| int width = graphTexture.width; | |
| for (int i = 0; i < allocated.Count; i++) { | |
| int x = i; | |
| float scale = graphHeight / (float)Math.Max(maxReserved, 1); | |
| int hMono = (int)(mono[i] * scale); | |
| int hAlloc = (int)(allocated[i] * scale); | |
| int hRes = (int)(reserved[i] * scale); | |
| for (int y = 0; y < hRes; y++) { | |
| int idx = x + y * width; | |
| pixels[idx] = y < hMono ? monoColor : | |
| y < hAlloc ? allocatedColor : reservedColor; | |
| } | |
| if (gcEvents[i]) { | |
| for (int y = 0; y < graphHeight; y++) { | |
| pixels[x + y * width] = gcEventColor; | |
| } | |
| } | |
| } | |
| if (gcEvents.Count > 2 && gcEvents[gcEvents.Count - 1]) { | |
| long monoDiff = mono[mono.Count - 2] - mono[mono.Count - 1]; | |
| Debug.Log($"GC Event detected! Mono memory change: {monoDiff / BYTES_TO_MB:F2} MB"); | |
| } | |
| graphTexture.SetPixels32(pixels); | |
| graphTexture.Apply(false); | |
| } | |
| void UpdateText(TMPro.TextMeshProUGUI text, float value, Color color) { | |
| if (!text) return; | |
| text.text = $"{value:F1} MB"; | |
| text.color = color; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment