░ ▒ ▓ █ Heap Explorer - Memory Profiler, Debugger and Analyzer for Unity

Introduction

Heap Explorer is a Memory Profiler, Debugger and Analyzer for Unity.

I spent a significant amount of time identifying and fixing memory leaks, as well as looking for memory optimization opportunities in Unity applications in the past. During this time, I often used Unity’s old Memory Profiler and while it’s an useful tool, I was never entirely happy with it.

This lead me to write my own memory profiler, where I have the opportunity to make all the things I didn’t like about Unity’s Memory Profiler better™.

Fast-forward about a year, Unity Technologies announced they’re working on a Memory Profiler too. This crushed my plans with what I had in mind for Heap Explorer. It was no longer a legit option for me to put a lot of time in the tool, as Unity Technologies tool is at least as good as what I’m able to come up with.

After a lot of back-and-forth what to do with Heap Explorer, I came to the conclusion that the best option is to provide the source code and (mentally) move on. You can read more about this [here]( ░ ▒ ▓ █ Heap Explorer - Memory Profiler, Debugger and Analyzer for Unity page-3#post-4250698).

I’ve provided occasional updates since then, because several people still prefer Heap Explorer over Unity’s Memory Profiler, due to its easier to understand UI/UX.

I’ve also updated Heap Explorer to work with Unity 2019.3 and converted it into a Package, which means you should be able to use Heap Explorer for the entire 2019 LTS cycle, which ends in 2022.

Don’t miss to watch this forum thread, in order to get notified about future updates!

Download

Heap Explorer for Unity 2019.3 is provided as an Unity Package:

Heap Explorer for Unity 2017.4 - 2019.2 is provided as a ZIP only:
http://www.console-dev.de/bin/heapexplorer.zip

Source code

Heap Explorer for Unity 2019.3:

Heap Explorer for Unity 2017.4 - 2019.2:
https://bitbucket.org/pschraut/unityheapexplorer/src

Known Issues

Heap Explorer uses Unity’s experimental MemoryProfiling API, which contains various bugs, from cosmetics to major issues that make memory snapshots not trustworthy.

These bugs occur in every application that use Unity’s MemoryProfiling API, such as Unity’s own MemoryProfiler tool. I hope Unity Technologies is going to fix them.

22 Likes

[reserved for future content]

[reserved for future content]

Very interesting, I’d love to get my hands on this tool when it comes out. I work on an Enterprise-orientated application that involves constant data streaming and our resource handling systems are quite complex. Especially now that we’re targeting WebGL it’s been critical for us to keep memory low.

I’ve used Unity’s MemoryProfiler (a somewhat modified version so it doesn’t struggle as badly with a large memory heap in use) but I’ve found it limiting. I particularly like the sound of the delegates view as I wouldn’t be surprised if we have a couple of those leaking in places.

Do you have a rough timeline of further work on the tool before you consider publishing it on the Asset Store - perhaps even with a price in mind? Personally I could already see it being valuable enough to a lot of people in its current state.

Out of curiosity, what .NET “target” have you been testing with? The only reason I ask is because I was quite surprised to see strings not sharing memory, and am wondering if this was an issue with the older Mono runtime or if it’s actually something Unity is doing internally.

If you’re interested, I can send you an alpha build. The tool works with Unity 2017.3 and newer only though. The current alpha build works with the limited amount of projects I have and tested it with, except for the various known Unity issues I documented in the manual.

I’m particularly interested to see whether Heap Explorer works with complex projects, because this was one of my goals to make possible. Heap Explorer does have a lower memory footprint than Unity’s MemoryProfiler and saves as well as loads memory snapshots faster according to my tests.

The “Delegates” and “Object Duplicates” views caused many “Oh really” moments on my end, I found them very insightful.

It pretty much depends on Unity Technologies and whether they’re able to repair their Memory Profiling API, which, to be fair, is marked as experimental.

However, I would feel very uncomfortable to provide a tool through the Unity Asset Store, where I know that it’s built on top of an API that does not work in various cases. I mean, their Memory Profiling API seems to report incorrect data in some cases, which makes memory snapshots not very trustworthy. The “good” thing is that it affects every tool, including Unity’s MemoryProfiler as well.

If Unity Technologies repairs their software and provides an API that does work, that would be moment when I would consider to provide it through the Asset Store.

Until then, I would like to test the tool in a smaller circle to collect feedback.

I tested with “Stable (.NET 3.5 Equivalent)”.

Btw, thanks for your post!

I’d definitely be interested in the alpha build - even if it’s not a bit experimental I’m sure it’d still give us some interesting insights nonetheless, even if some of the memory sizes and connections are occasionally out of whack. We upgraded to 2017.3 not too long ago so we should be good to go. I’m surprised there hasn’t been more interest yet.

Please check your private messages, I just sent you a download link to the tool.

I developed Heap Explorer with/for Unity 2017.3.0f3. I recently upgraded Unity to 2017.3.1p2 and Unity seem to have introduced a new crash bug in a version between 0f2 and 1p2 :face_with_spiral_eyes: The crash occurs when capturing a memory snapshot of a build. It works in the editor though.

If someone from Unity Technologies is reading this, please fix this crash:
(Case 993250) PackedMemorySnapshot: RequestNewSnapshot causes Player to crash

I’d be very interested too - I’ve tried Unity’s profiler on Bitbucket, and found it practically unusable for a moderately-sized project (we have a ~2gb footprint, and will get a lot bigger before we ship).

I just saw a new talk announced for Unite Berlin, entitled “Memory Profiler: The Tool for Troubleshooting Memory-Related Issues” - I wonder if they’re working on a new profiler, or if they’re just going to talk about the existing one?

I just sent you a private message with a download link.

Interesting! I hope it’s going to be available on youtube as well.

1 Like

I’m sure it will, they’re pretty good at uploading every Unite talk to YouTube pretty quickly after the conference.

Hello,

I tried it on few of my personal small projects, and there is immediately one feedback I can give - being able to do a capture and save it right away without analyzing it automatically after capture would be cool - especially on a large projects, when I guess the analyzing would take a long time, and I would rather do a few snapshots quickly during gameplay, and analyze them later separately, e.g. by using the diffing feature. :slight_smile:

Also, when I tested it with 2018.1, I immediately stumbled upon an exception when analyzing the snapshot.

IndexOutOfRangeException: Array index is out of range.
HeapExplorer.PackedManagedObjectCrawler.InitializeCachedPtr () (at c:/Users/crash/Documents/HeapExplorer/Code/Code/PackedTypes/PackedManagedObjectCrawler.cs:65)
HeapExplorer.PackedManagedObjectCrawler.Crawl (HeapExplorer.PackedMemorySnapshot snapshot)

If it’s not reproducible for you with 2018.x (since you said 2017.3 and newer), I’m willing to send you the project where I’m hitting the exception privately, since it is just a personal student project I did like a year ago or so. :slight_smile:

1 Like

Were you able to successfully capture and analyze a snapshot with Heap Explorer of any of your projects?

That’s actually a very useful feature. It’s implemented in alpha 1.2, which you can download using the original download link I sent you in the very first message.

I was able to reproduce this issue with 2018.1.0f2 and 2018.2.0b3.

It seems they introduced a new bug in 2018.1 where the types description array in the memory snapshot is empty. I just reported this issue:
https://discussions.unity.com/t/700649

That’s a bummer, because the MemoryProfiling API did work in various 2018.1 beta builds (or this specific issue did not exist). Sad to see how it falls further apart with pretty much every Unity update. It seems there is no recent Unity build anymore, where the API actually works.

I really hope UT is going to fix these issues soon, but it’s more likely that it takes a few months before those fixes land.

Thank you for giving Heap Explorer a try and providing feedback. I really appreciate that!

1 Like

Yes, but since I have the 2017.3.1p2 installed, I just tried the in-editor capture. I just wanted to look at the UI and get familiar with it a little bit, before I try it on the bigger project I wrote you about in PM. :slight_smile:

I hope I’ll be able to capture the bigger project from a build and not editor, but I’m afraid I’ll hit the stupid “Receiver can not keep up with the amount of data sent” error we also hit when I tried the Unity’s profiler (which is something you can’t do nothing about, since it is an issue with profiler directly).

Thanks!

Let’s hope for a fast fix then. Being able to profile just a few specific versions of 2017.3 would really really suck. :confused:

No, thank YOU for making it!

2 Likes

Good to hear that it works at least in the editor on a different computer than mine.

I’m looking forward to what you’ll find out. I hope this affects the Profiler only, but not the MemoryProfiling API, but I don’t know. If you run into this issue, perhaps it makes a difference if you turn off ‘Record’ in the Profiler window, if it’s enabled at all.

I’ve just uploaded an update again. Here is the changelog…

  • In Heap Explorer toolbar, added “Capture > Open Profiler”. This opens the Unity profiler window, which allows you to connect to a different target. It’s simply a convenience feature, if you figure the editor is not connected to the correct player. This saves a few mouse clicks to get to the Unity Profiler to connect to a different player.
  • The “Load” button in the “Compare Snapshot” view now features a “most recently used” snapshot list. This allows you to switch between previously saved snapshots with fewer mouse clicks.
  • In Heap Explorer toolbar, added “File > Recent”, which allows to re-open the “most recently used” snapshots.

Here’s some more feedback I stumbled upon:

  • Be able to swap (A) and (B) in compare easily. It is “intuitive” to load “before leak” capture first and “after leak” as second, but to see the diffs more clearly and not inversed, you should load it in the other direction to get B-A, not A-B. :slight_smile:
  • Do not reset current View when opening a new snapshot.
  • Do not reset the chosen second snapshot in diff view. Wouldn’t mind unloading it when exiting the view, but analyze + load it back again when returning to the view? But I understand this might make things slow with really really big captures. Maybe a checkbox to “remember selection” could be an ok solution?
  • Click on Address in Memory Sections view does not open its detail, but acts as a rename.
  • And a totally cool feature would be the option to filter the results by selecting some new “root” object, and show objects that are referenced by the selected object (recursively), to e.g., see how much some terrain engine/audio engine/etc. takes memory (approximately) - with the option to optionally remove some objects from the hierarchy, so you don’t reference a global manager and include all the other subsystems anyways.
  • Sums of the filtered size/count rows.

Other than that, the tool is really really superb!

Great, thanks a heap! :slight_smile:

I uploaded a new version that contains some of your feedback. I didn’t implement all feedback yet, I went with the low hanging fruit first…

Here is the 1.6 change log:

  • “Compare Snapshots”, changed diff from “A - B” to “B - A” comparison. I’ll see how to best implement your suggestion to swap snapshots and avoid unloading the other one.

  • Fixed being unable to select a memory section by clicking on its address in the “C# Memory Sections” view. I used a selectable label first, to be able to select and copy the address. I’ve implement a context menu when right-clicking on any address now that provides a “Copy” option.

  • Loading a snapshot restores the previously active view when done. I still throw out the old snapshot before loading, to release its memory. So while the snapshot is loading, no view is visible except for the “Loading…” label. But it restores the view afterwards.

Can you elaborate on this please? I don’t get it :slight_smile:

Is this meant for the existing views or for the “option to filter the results by selecting some new root object” suggestion? What do you mean with “filtered”, if you enter a search text? Or is this perhaps meant for the “Compare Snapshot” view only?

Thanks again!

PS: Unity was able to reproduce the “missing typeDescriptions” problem, why heap explorer doesn’t work with 2018.1. Turns out this affects a lot more Unity releases, but not 2017.3 (yet). It’s even broken in their long term support stream 2017.4, so nobody is able to take a memory snapshot with any tool using the most stable Unity release yet. :hushed: Crossing fingers that it’s being fixed soon.

I’ll try to do a more textual description of the thing, with a made up example. :slight_smile:

Ok, let’s say you have some “GameManager”, that has fields that contains “WorldManager” and “MusicManager” (among others), but also both managers have a “backref” to the GameManager. The “WorldManager” has some data about spawned objects (units/vegetation/…), and also data about terrain, and the terrain data has arrays.

Being able to select “WorldManager” as a root object, and calculate how much memory does it + all the objects it references contain (recursively), would make it easy to determine for example that your world engine part of the game takes approximately ~100 MB. Being able to exclude some objects from this calculation is however really important - for example for the references to other systems (either directly or through back references), so you can “isolate” the memory footprint of a subsystem.

Basically, in a pseudocode, something like this.

Queue toVisit = { worldManagerInstance };
while (toVisit.Count > 0) {
    var obj = toVisit.Pop();
    if (typesToSkip.Contains(obj.Type)) continue;
    foreach (var field in obj) {
        if (field.IsReferenceType) toVisit.Push(field.Value); // adds the pointer/whatever to the queue to visit it later
    }
}

Basically for any view in the datagrid, having a sum size/count rows at the bottom would be beneficial. If you do not filter using the search, you get the value next to the search bar, e.g. 110243 managed objects, 11.7 MB memory; so these numbers reacting on any “filtered” view (e.g. by the root selection mentioned previously, or text search), would make it again much better in usability IMHO.

Also, there’s a bug when I sometimes need to type twice (when deleting the text in between searches) to actually make it trigger the search, but I can’t find a repro. :frowning:

Cool!

I tried capture today with our bigger project (in a huge level, it downright crashed the editor after a few seconds - submitted a bug report to unity, we’ll see what I’ll get as a reply). For a smaller level it felt like some data were missing in the snapshot? Like, by doing a calculation about the number of objects/arrays I definitely know of, it showed like ~1/5th of them? Is it possible there are some objects in the snapshots missing? Also, there was a HUGE disparity in the profiler vs heap explorer. The HE showed 35.8MB managed memory in the managed objects, the managed sections said 0.5GB over 3.5GB fragmented, and profiler showed these numbers:

1 Like

Thank a lot for the explanation. I get it now! :slight_smile:

Did you get a reply, what did they say?

Maybe it’s related to the custom type in your project where Heap Explorer has problems with? As far as I remember it’s an array, perhaps it pulls in these managed objects which are missing, because the array is ignored?

Other than that, if you’re able to capture a memory snapshot using Unity’s Memory Profiler from bitbucket and send me that screenshot, I can check if the managed object crawler in HE is reconstructing different objects than Unity’s tool.

That was basically my approach when I implemented the object crawler. I captured a memory snapshot using Unity’s MemoryProfiler. Then I extended their profiler to output all managed objects (addresses, type, etc) to a file which I imported in Heap Explorer. Then I was able to read the original snapshot of Unity’s Memory Profiler, run the managed object crawler in Heap Explorer over it and afterwards compare if HE reconstructed the same objects as Unity’s MemoryProfiler.

The object crawler is a complicated beast, perhaps it still has some issues, even though all the snapshots I tested were ok. If you can provide the Unity MemoryProfiler snapshot, I can use it to check whether Heap Explorer is failing.

I came across this one as well and reported it a while ago as:
https://issuetracker.unity3d.com/issues/packedmemorysnapshot-managedheapsections-do-not-match-stats-in-profiler

It’s not possible to “reconstruct” the numbers of Unity’s Profiler using their MemoryProfling API.

Which further leads to this issue:
https://issuetracker.unity3d.com/issues/executableanddlls-is-not-found-when-checking-through-a-packedmemorysnapshot

…and eventually to this issue, if you use 2018.1 and native buffers:
https://issuetracker.unity3d.com/issues/packedmemorysnapshot-nativearray-memory-is-not-captured-in-it

Haven’t replied yet. I’ll keep you posted when they do. :slight_smile:

Actually, in this case, this is an array of struct that has two byte values. By a simple calculation, there should be around ~150MB of this byte data arrays, but it shows only ~45MB. I’ll try to do more experiments and stuff next week though, and will probably iterate over the all known arrays of this type manually and debug.log how many are there and how many the heap explorer shows (maybe my calculation was just wrong).

The Unity memory profiler wasn’t even able to capture a main menu for us, yet alone the level.

1 Like

So, yeah, the snapshot really doesn’t contain everything. :frowning:

I counted it using for loop and traversing, and it displayed about ~15% only of the byte data there really is (~2500 instances instead of ~20k).

The good thing is that it’s really easily reproducible. Just create a new project and put this script on some object, enter play mode and then do a capture. But I guess this will be an issue with Unity’s API and not your Heap Explorer anyways? :frowning:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public struct ByteData
{
    byte v1;
    byte v2;
}

public struct Block
{
    public ByteData[][] Datas;
}

public class Test : MonoBehaviour
{

    public Block[] Blocks;

    void Start()
    {
        Blocks = new Block[75];
        for (int i = 0; i < 35; i++)
        {
            Blocks[i].Datas = new ByteData[512][];
            for (int j = 0; j < 500; j++)
            {
                Blocks[i].Datas[j] = new ByteData[4096];
            }
        }
    }
}
1 Like