Skip to content

Instantly share code, notes, and snippets.

@HenningTimm
Last active January 14, 2024 15:58
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save HenningTimm/ab1e5c05867e9c528b38599693d70b35 to your computer and use it in GitHub Desktop.
Save HenningTimm/ab1e5c05867e9c528b38599693d70b35 to your computer and use it in GitHub Desktop.
Memory profiling Rust code with heaptrack in 2019

Memory profiling Rust code with heaptrack in 2019

I recently ran into a classic case of "our code is using way more memory than it should". So I took my first dive into memory profiling Rust code. I read several posts about this, including the following

which mentioned valgrind massif, jemalloc's ex_stats_print, flamegraphs and heaptrack. I tried heaptrack first, because it looked like the simplest solution to me. The tutorials I found all required changing the allocator, since heaptrack doesn't play nicely with jemalloc. But, good news: Since Rust 1.32, jemalloc has been replaced as default allocator by the system default allocator. This means that we no longer need to make changes to our code for memory profiling.

So, lets take a look at memory profiling.

Installing heaptrack

On Ubuntu (18.04) heaptrack can be installed via apt:

sudo apt install heaptrack heaptrack-gui

Alternatively, (and for older ubunutu versions) you can download the source code from the github repo and compile it yourself.

Running your code

Use heaptrack to run your binary and pass parameters as usual. An example for our code looks like this:

heaptrack target/release/rbt call-consensus-reads 1.fq.gz 2.fq.gz out.1.fq.gz out.2.fq.gz

heaptrack output will be written to "heaptrack.rbt.17073.gz"
starting application, this might take some time...
[some output of rbt]
Heaptrack finished! Now run the following to investigate the data:

  heaptrack --analyze "heaptrack.rbt.17073.gz"

As you can see I used the release build (without any debug symbols). Using heaptrack slows down execution of the program, but not in a huge way. In my case, running without heaptrack took ~4 minutes, running with heaptrack took 6-7 minutes.

Running heaptrack like this writes a gzipped result file that we can now take a look at.

Visualizing results

The heaptrack_gui tool generates a nice collection of graphs and different views on the results.

You might need to wait a little for all graphs to render. Unfinished tabs stay grayed-out until they are done.

heaptrack_gui heaptrack.rbt.17073.gz

For my specific problem, I used the "Consumed" tab to take a look at memory usage over time and was able to identify the culprit: SliceConcatExt

heaptrack_before

Other visualizations include a flamegraph, bottom-up and top-down lists, and a nice summary which also identified SliceConcatExt as the peak contributor to memory load consumption.

Fixing my problem

In our case, with the information that we need to look for a concatenation of slices we quickly found some old code that could be refactored to work without these additional allocations. After fixing our code, I ran heaptrack again and, as you can see, peak memory use dropped from 400MB to <100MB, leveling out at about 60MB instead of 380MB.

heaptrack_after

Conclusion

Since Rust 1.32 (January 2019), heaptrack works out of the box for memory profiling Rust code. You no longer need to change the allocator, making it a nice and quick way to take a look at you memory footprint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment