Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Visualizing Garbage Collection in Ruby and Python

DZone's Guide to

Visualizing Garbage Collection in Ruby and Python

· Performance Zone
Free Resource

Transform incident management with machine learning and analytics to help you maintain optimal performance and availability while keeping pace with the growing demands of digital business with this eBook, brought to you in partnership with BMC.

This is a post that Pat Shaughnessy wrote based on his presentation at RuPy in Budapest. You can also watch the video recording of the presentation. This recap was originally published on his personal blog, and we are republishing it on Codeship with his kind permission.

circuitory-system-polish
If your algorithms and business logic
are the brain of your application, which
organ would the garbage collector be?

Since this is the “Ruby Python” conference, I thought it would be fun to compare how garbage collection works inside of Ruby and Python.

But before we get to that, why talk about garbage collection at all? After all, it’s not the most glamorous, exciting topic, is it? How many of you get excited by garbage collection? [ A number of RuPy attendees actually raised their hands! ]

Recently in the Ruby community there was a blog post about how you can speed up your unit tests by changing your Ruby GC settings. I think that’s great. It’s good to run your tests faster and to run your app with fewer GC pauses, but somehow GC doesn’t really excite me. It seems like a boring, dry, technical topic at first glance.

But actually, garbage collection is a fascinating topic: GC algorithms are both an important part of computer science history and a subject of cutting edge research. For example, the Mark and Sweep algorithm used by MRI Ruby is over 50 years old, while one of the GC algorithms used inside of Rubinius, an alternative implementation of Ruby, was invented just recently in 2008.

However, the name “garbage collection” is actually misnomer.

The Beating Heart of Your Application

GC systems do much more than just “collect garbage.” In fact, they perform three important tasks. They:

  • allocate memory for new objects
  • identify garbage objects
  • reclaim memory from garbage objects

heart-polish

Imagine if your application was a human body: All of the elegant code you write, your business logic, your algorithms, would be the brain or the intelligence inside the application.

Following this analogy, what part of the body do you think the garbage collector would be? [ I got lots of fun answers from the RuPy audience: kidneys, white blood cells, etc. ]

I think the garbage collector is the beating heart of your application. Just as your heart provides blood and nutrients to the rest of your body, the garbage collector provides memory and objects for your application to use.

If your heart stopped beating you would die in seconds. If the garbage collector stopped or ran slowly — if it had clogged arteries — your application would slow down and eventually die!


Garbage Collection may not sound exciting, but it keeps your application from dying a slow death.

Click To Tweet

A Simple Example

It’s always helpful to work through theories using examples. Here’s a simple class, written in both Python and Ruby, that we can use as an example today:

code

By the way, it’s amazing to me how similar this code is in both languages. Ruby and Python are really just slightly different ways of saying the same thing. But are the languages also implemented in a similar way internally?

The Free List

When we call Node.new(1) above, what does Ruby do, exactly? How does Ruby go about creating a new object for us?

Surprisingly, it does very little! In fact, long before your code starts to run, Ruby creates thousands of objects ahead of time and places them on a linked list, called the free list. Here’s what the free list might look like, conceptually:

free-list1

Imagine each of the white squares above is an unused, precreated Ruby object. When we call Node.new, Ruby simply takes one of these objects and hands it to us:

free-list2

In the diagram above, the gray square on the left represents an active Ruby object we’re using in our code, while the remaining white squares are unused objects. Of course, my diagrams are a simplified version of reality. In fact, Ruby would use another object to hold the string “ABC,” a third object to hold the class definition of Node, and still other objects to hold the parsed, abstract syntax tree (AST) representation of my code, etc.

If we call Node.new again, Ruby just hands us another object:

free-list3

mccarthy
John McCarthy’s 1960 implementation
of Lisp contained the first garbage
collector. (Courtesy MIT Museum)

This simple algorithm of using a linked list of precreated objects was invented over 50 years ago by a legendary computer scientist named John McCarthy, while he was working on the original implementation of Lisp.

Lisp was not only one of the first functional programming languages but also contained a number of other groundbreaking advances in computer science.

One of these was the concept of automatically managing your application’s memory using garbage collection.

The standard version of Ruby, also known as “Matz’s Ruby Interpreter” (MRI), uses a GC algorithm similar to the one used by McCarthy’s implementation of Lisp in 1960. For better or worse, Ruby uses a 53-year-old algorithm for garbage collection. Just as Lisp did, Ruby creates objects ahead of time and hands them to your code when you allocate new objects or values.


“Did you know Ruby uses a 53-yr-old algorithm for garbage collection?” – via @pat_shaughnessy

Click To Tweet

Allocating Objects in Python

We’ve seen that Ruby creates objects ahead of time and saves them in the free list. What about Python?

While Python also uses free lists for various reasons internally (it recycles certain objects such as lists), it normally allocates memory for new objects and values differently than Ruby does.

Suppose we create a Node object using Python:

python1

Python, unlike Ruby, will ask the operating system for memory immediately when you create the object. (Python actually implements its own memory allocation system which provides an additional layer of abstraction on top of the OS heap. But I don’t have time to get into those details today.)

When we create a second object, Python will again ask the OS for more memory: python2

Seems simple enough; at the moment we create an object, Python takes the time to find and allocate memory for us.

mess
Ruby leaves unused objects
lying around in memory
until the next GC process runs.

Ruby Developers Live in a Messy House

Back to Ruby.

As we allocate more and more objects, Ruby will continue to hand us precreated objects from the free list.

As it does this, the free list will become shorter: free-list4

…and shorter:

free-list5

Notice as I continue to assign new values to n1, Ruby leaves the old values behind. The ABC, JKL, and MNO nodes remain in memory. Ruby doesn’t immediately clean up old objects my code is no longer using!

Working as a Ruby developer is like living in a messy house, with clothes lying on the floor or dirty dishes in the kitchen sink. As a Ruby developer you have to work with unused, garbage objects surrounding you.

clean
Python cleans up garbage objects
immediately after your code
is done using them.

Python Developers Live in a Tidy Household

Garbage collection works quite differently in Python than in Ruby.

Let’s return to our three Python Node objects from earlier: python3b

Internally, whenever we create an object, Python saves an integer inside the object’s C structure, called the reference count. Initially, Python sets this value to 1: python4

The value of 1 indicates there is one pointer or reference to each of the three objects. Now suppose we create a new node, JKL:

python5

Just as before, Python sets the reference count in JKL to be 1. However, also notice since we changed n1 to point to JKL, it no longer references ABC, and that Python decremented its reference count down to 0.

At this point, the Python garbage collector immediately jumps into action! Whenever an object’s reference count reaches zero, Python immediately frees it, returning its memory to the operating system:

python6

Python reclaims the memory used by the ABC node. Remember, Ruby simply leaves old objects lying around and doesn’t release their memory.

This garbage collection algorithm is known as reference counting. It was invented by George Collins in 1960 — not coincidentally the same year John McCarthy invented the free list algorithm.

As Mike Bernstein said in his fantastic presentation on garbage collection at the Gotham Ruby Conference: “1960 was a good year for Garbage Collectors….”

Working as a Python developer is like living in a tidy house; you know, the kind of place where your roommates are constantly cleaning up after you. As soon as you put down a dirty dish or glass, someone puts it away in the dishwasher!

Now for a second example. Suppose we set n2 to refer to the same node as n1: python8

You can see Python has decremented the reference count for DEF and will immediately garbage collect the DEF node. Also note that the JKL now has a reference count of 2, since both n1 and n2 point to it.

Try Codeship – The simplest Continuous Delivery service out there.

Mark and Sweep

Eventually a messy house fills up with trash, and life can’t continue as usual. After your Ruby program runs for some time, the free list will eventually be entirely used up:

mark-and-sweep1

All of the precreated Ruby objects have been used by our application (they are all gray), and no objects remain on the free list (no white squares are left).

At this point Ruby uses another algorithm invented by McCarthy known as Mark and Sweep. First Ruby stops your application; Ruby uses “stop the world garbage collection.” Ruby then loops through all of the pointers, variables, and other references our code makes to objects and other values. Ruby also iterates over internal pointers used by its virtual machine. It marks each object that it is able to reach using these pointers. I indicate these marks using the letter M here:

mark-and-sweep2

The three objects marked with “M” are live, active objects that our application is still using. Internally, Ruby actually uses a series of bits known as the free bitmap to keep track of which objects are marked or not:

mark-and-sweep3

Ruby saves the free bitmap in a separate memory location in order to take full advantage of Unix copy-on-write optimization. For more information on this, see my article “Why You Should Be Excited About Garbage Collection in Ruby 2.0.”

If the marked objects are live, the remaining, unmarked objects must be garbage, meaning they are no longer being used by our code. I’ll show the garbage objects as white squares below:

mark-and-sweep4

Next Ruby sweeps the unused, garbage objects back onto the free list:

mark-and-sweep5

Internally this happens quite quickly, since Ruby doesn’t actually copy objects around from one place to another. Instead, Ruby places the garbage objects back onto the free list by adjusting internal pointers to form a new linked list.

Now Ruby can give these garbage objects back to us the next time we create a new Ruby object. In Ruby, objects are reincarnated and enjoy multiple lives!

Mark and Sweep vs. Reference Counting

At first glance, Python’s GC algorithm seems far superior to Ruby’s: Why live in a messy house when you can live in a tidy one? Why does Ruby force your application to stop running periodically each time it cleans up, instead of using Python’s algorithm?

Reference counting isn’t as simple as it seems at first glance, however. There are a number of reasons why many languages don’t use a reference counting GC algorithm like Python does:

  • First, it’s difficult to implement. Python has to leave room inside of each object to hold the reference count. There’s a minor space penalty for this. But worse, a simple operation such a changing a variable or reference becomes a more complex operation since Python needs to increment one counter, decrement another, and possibly free the object.
  • Second, it can be slower. Although Python performs GC work smoothly as your application runs (cleaning dirty dishes as soon as you put them in the sink), this isn’t necessarily faster. Python is constantly updating the reference count values. And when you stop using a large data structure, such as a list containing many elements, Python might have to free many objects all at once. Decrementing reference counts can be a complex, recursive process.
  • Finally, it doesn’t always work. As we’ll see in my next post containing my notes from the rest of this presentation, reference counting can’t handle cyclic data structures — data structures that contain circular references.

Until Next Time…

Next week I’ll type up the rest of the presentation. I’ll discuss how Python handles cyclic data structures, and how GC works in the Ruby 2.1 release.

Evolve your approach to Application Performance Monitoring by adopting five best practices that are outlined and explored in this e-book, brought to you in partnership with BMC.

Topics:
garbage collection ,ruby ,python ,performance

Published at DZone with permission of Moritz Plassnig, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}