Analyzing ineffective memory usage with dotMemory
Memory issues in .NET apps can be generally divided into 1) leaks, 2) large memory traffic, and 3) ineffective memory usage. In this blog we’ve written extensively about how dotMemory helps you deal with the first two problems. Now it’s time to talk about analyzing ineffective memory usage, especially since dotMemory 4.3 introduces a new feature that can help really fight this issue.
Before we move on, let’s agree on some terms. What is ineffective usage? When your app consumes more memory than it should, or could, we call this ineffective. Sometimes you just “feel” that a particular algorithm consumes too much, but nothing seems to explain why it does. That’s when you should use a memory profiler.
Assume you’ve done the profiling and taken a memory snapshot. What next?
There are two main analysis approaches that may be combined or used independently. Start by answering the following two questions:
What methods allocate the most memory?
What objects retain the most memory?
What Methods Allocate Most of the Memory?
This task is typically solved with the help of a call tree. Most memory profilers have a special call tree view for determining the calls that allocated the largest amounts of memory. Doesn’t sound too complicated, does it? But in fact, even for mid-size projects digging into the tree may become a headache.
Of course, this algorithm is applicable to dotMemory as well. However, since version 4.0 we offer a much easier way called Call Tree as Icicle Chart.
The idea behind the chart is simple – it’s a graphical representation of the call tree. Each call is shown as a horizontal bar whose length depends on the size of objects allocated in the call’s subtree. The more memory allocated in the underlying subtree, the longer the bar. The bar’s color value serves as an additional indicator – the more memory allocated by the call itself, the darker the bar.
So instead of looking at lots of numbers, start your analysis by opening the Call Tree as Icicle Chart view. In just a glance you can match a certain call with the amount of memory it allocates.
For example, the following chart shows the same data as the Call Tree table in the GIF above. Notice how there’s no need to dive into the call tree: main memory allocations can be seen instantly.
Of course the chart is interactive: just click a call to see its call stack. Use the Shift+click combination to zoom into the chart and examine the call chain in more detail.
Who Retains the Memory?
Understanding how memory is retained in your app is essential for successful optimization. For example, you know that a major part of memory in your app is consumed by strings. Nevertheless, most likely, the subject of your optimizations is not these strings by themselves but the data structures that store them. That’s why “Who retains the memory?” is the next big question when analyzing ineffective memory usage.
Objects that exclusively retain other objects in memory are called dominators. Earlier dotMemory versions offered just one way of analyzing app dominators – the Group by Dominators view, which shows the tree of dominators sorted by retained memory size:
Since version 4.3, dotMemory offers a new “visual” way of analyzing dominators: the Sunburst Chart. In this view, the dominators hierarchy is shown on a sunburst chart. The more memory a dominator retains, the larger the central angle.
A quick look at this chart shows what objects are crucial for your app and helps you evaluate the largest structures.
If you click on a particular dominator, the Domination Path on the right will show you the retention path of this dominator. Double-click on a dominator to zoom into the chart and see the objects retained by this dominator in more detail.
Our experience shows that Dominators chart is also very effective when you need to quickly evaluate how a certain functionality works in your app. For example, below are two charts built for an image editor application: the first one was plotted before anything is done in the app, and the second one reflects memory usage after the user has applied an image filter.
After some time, if you profile your app constantly, you’ll even be able to “see” not only how your app works, but even how particular changes in code affect memory usage.
Thanks for reading! We hope this post was helpful. Give dotMemory 4.3 a try and explore the Dominators and Call Tree charts.
Subscribe to Blog updates
Thanks, we've got you!
Eager, Lazy and Explicit Loading with Entity Framework Core
Entity Framework Core (EF Core) supports a number of ways to load related data. There’s eager loading, lazy loading, and explicit loading. Each of these approaches have their own advantages and drawbacks. In this post, let’s have a quick look at each of these ways to load data for navigational prope…
OSS Power-Ups: bUnit – Webinar Recording
The recording of our webinar, OSS Power-Ups: bUnit, with Egil Hansen and Steven Giesel, is available. This was the twelfth episode of our OSS Power-Ups series, where we put a spotlight on open-source .NET projects. Subscribe to our community newsletter to receive notifications about future webi…
Accelerating Your Testing Workflow with Unit Test Creation and Navigation
Unit tests play an important role in our daily development workflow. They help us ensure our codebase's correctness when writing new functionality or performing refactorings to improve readability and maintainability. In the process, we often create new test files that accompany the p…
Introducing Predictive Debugging: A Game-Changing Look into the Future
With the introduction of debugging tools, software developers were empowered to interactively investigate the control flow of software programs to find bugs in live environments. At JetBrains, we've always strived to improve the art of debugging. Besides the more standard things you expect from a de…