What is memprof and why do I care?

If you enjoy this article, subscribe (via RSS or e-mail) and follow me on twitter.

memprof is a Ruby gem which supplies memory profiler functionality similar to bleak_house without patching the Ruby VM. You just install the gem, call a function or two, and off you go.

Where do I get it?

memprof is available on gemcutter, so you can just:

gem install memprof

Feel free to browse the source code at: http://github.com/ice799/memprof.

How do I use it?

Using memprof is simple. Before we look at some examples, let me explain more precisely what memprof is measuring.

memprof is measuring the number of objects created and not destroyed during a segment of Ruby code. The ideal use case for memprof is to show you where objects that do not get destroyed are being created:

Objects are created and not destroyed when you create new classes. This is a good thing.

Sometimes garbage objects sit around until garbage_collect has had a chance to run. These objects will go away.

has had a chance to run. These objects will go away. Yet in other cases you might be holding a reference to a large chain of objects without knowing it. Until you remove this reference, the entire chain of objects will remain in memory taking up space.

memprof will show objects created in all cases listed above.

OK, now Let’s take a look at two examples and their output.

A simple program with an obvious memory “leak”:

require 'memprof' @blah = Hash.new([]) Memprof.start 100.times { @blah[1] << "aaaaa" } 1000.times { @blah[2] << "bbbbb" } Memprof.stats Memprof.stop

This program creates 1100 objects which are not destroyed during the start and stop sections of the file because references are held for each object created.

Let's look at the output from memprof:

1000 test.rb:11:String 100 test.rb:7:String

In this example memprof shows the 1100 created, broken up by file, line number, and type.

Let's take a look at another example:

require 'memprof' Memprof.start require "stringio" StringIO.new Memprof.stats

This simple program is measuring the number of objects created when requiring stringio .

Let's take a look at the output:

108 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:__node__ 14 test2.rb:3:String 2 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:Class 1 test2.rb:4:StringIO 1 test2.rb:4:String 1 test2.rb:3:Array 1 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:Enumerable

This output shows an internal Ruby interpreter type __node__ was created (these represent code), as well as a few String s and other objects. Some of these objects are just garbage objects which haven't had a chance to be recycled yet.

What if nudge the garbage_collector along a little bit just for our example? Let's add the following two lines of code to our previous example:

GC.start Memprof.stats

We're now nudging the garbage collector and outputting memprof stats information again. This should show fewer objects, as the garbage collector will recycle some of the garbage objects:

108 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:__node__ 2 test2.rb:3:String 2 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:Class 1 /custom/ree/lib/ruby/1.8/x86_64-linux/stringio.so:0:Enumerable

As you can see above, a few String s and other objects went away after the garbage collector ran.

Which Rubies and systems are supported?

Only unstripped binaries are supported. To determine if your Ruby binary is stripped, simply run: file `which ruby` . If it is, consult your package manager's documentation. Most Linux distributions offer a package with an unstripped Ruby binary.

binaries are supported. To determine if your Ruby binary is stripped, simply run: . If it is, consult your package manager's documentation. Most Linux distributions offer a package with an unstripped Ruby binary. Only x86_64 is supported at this time. Hopefully, I'll have time to add support for i386/i686 in the immediate future.

is supported at this time. Hopefully, I'll have time to add support for i386/i686 in the immediate future. Linux Ruby Enterprise Edition (1.8.6 and 1.8.7) is supported.

Linux MRI Ruby 1.8.6 and 1.8.7 built with --disable-shared are supported. Support for --enable-shared binaries is coming soon.

Snow Leopard support is experimental at this time.

at this time. Ruby 1.9 support coming soon.

How does it work?

If you've been reading my blog over the last week or so, you'd have noticed two previous blog posts (here and here) that describe some tricks I came up with for modifying a running binary image in memory.

memprof is a combination of all those tricks and other hacks to allow memory profiling in Ruby without the need for custom patches to the Ruby VM. You simply require the gem and off you go.

memprof works by inserting trampolines on object allocation and deallocation routines. It gathers metadata about the objects and outputs this information when the stats method is called.

What else is planned?

Myself, Jake Douglas, and Aman Gupta have lots of interesting ideas for new features. We don't want to ruin the surprise, but stay tuned. More cool stuff coming really soon :)

Thanks for reading and don't forget to subscribe (via RSS or e-mail) and follow me on twitter.