Saving 9 GB of RAM with Python’s __slots__

by Ben on November 17, 2013

We’ve mentioned before how Oyster.com’s Python-based web servers cache huge amounts of static content in huge Python dicts (hash tables). Well, we recently saved over 2 GB in each of four 6 GB server processes with a single line of code — using __slots__ on our Image class.

Here’s a screenshot of RAM usage before and after deploying this change on one of our servers:

RAM usage before and after deploying this change

We allocate about a million instances of a class like the following:

class Image(object):
    def __init__(self, id, caption, url):
        self.id = id
        self.caption = caption
        self.url = url
        self._setup()

    # ... other methods ...

By default Python uses a dict to store an object’s instance attributes. Which is usually fine, and it allows fully dynamic things like setting arbitrary new attributes at runtime.

However, for small classes that have a few fixed attributes known at “compile time”, the dict is a waste of RAM, and this makes a real difference when you’re creating a million of them. You can tell Python not to use a dict, and only allocate space for a fixed set of attributes, by settings __slots__ on the class to a fixed list of attribute names:

class Image(object):
    __slots__ = ['id', 'caption', 'url']

    def __init__(self, id, caption, url):
        self.id = id
        self.caption = caption
        self.url = url
        self._setup()

    # ... other methods ...

Note that you can also use collections.namedtuple, which allows attribute access, but only takes the space of a tuple, so it’s similar to using __slots__ on a class. However, to me it always feels weird to inherit from a namedtuple class. Also, if you want a custom initializer you have to override __new__ rather than __init__.

Warning: Don’t prematurely optimize and use this everywhere! It’s not great for code maintenance, and it really only saves you when you have thousands of instances.

  • fijal

    hey guys. did you try running your stuff on PyPy? it does the __slots__ like optimization without using __slots__ for example

    • http://benhoyt.com/ Ben Hoyt

      No, we haven’t tried PyPy yet, though we’re definitely keeping our eye on it! Incidentally, Python 3.3 mitigates this issue somewhat with PEP 412′s “key-sharing dictionary” implementation: http://www.python.org/dev/peps/pep-0412/ It’s not as good as slots, but it uses about half the memory of ordinary classes in a quick test I did.

      • fijal

        you guys should probably at least give it a go (especially if CPU performance is also an issue). I would be keen to know the results, contact me at fijall at gmail

    • http://www.theroadtosiliconvalley.com/ Ernest Semerda

      What other optimizations does PyPy do? You got my attention with your comment :-)

      • fijal

        sorry was flying. tons of optimizations – most to preserve CPU time, but some to preserve memory too (small tuple are type specialized as are lists for example)

        • http://www.theroadtosiliconvalley.com/ Ernest Semerda

          Awesome! Will give it a test. Cheers!

    • http://www.hokstad.com/blog vidarh

      I’m working on a Ruby compiler and have taken pretty much that approach: Any instance variable names I can statically determine are candidates for the equivalent treatment (allocating a slot at a fixed offset in the object structure). Anything else will still go in a dictionary. In practice my experience is that a huge proportion of objects will have a fairly static set of attributes, and the dynamic set is often small enough that having pointers them included in every instance is still often cheaper than using dictionaries.

  • Guest

    Unfortunately this results in your images being squeezed in the vice of micromanagement, my avatar a case in point.

  • webreac

    I feel this __slots__ keyword is not only a speed optimization, but also a reliable documentation of all the fields of the class. This could help for the maintenance. Why do you think this is not great for maintenance ?

    • http://benhoyt.com/ Ben Hoyt

      Interesting point — I’m not sure __slots__ is intended to be used for documentation, but that’s a fair idea. I said it wasn’t great for maintenance because you end up specifying the field names twice (not DRY). namedtuple kind of gets around this.

  • Chris Colbert

    This is why I wrote Atom: https://github.com/nucleic/atom

    You could slots-like behavior by default, plus type-checking and an efficient observer pattern.

    • Chris Colbert

      s/could/get

    • Terrence Brannon

      Atom looks very nice. Thanks for the link!

    • Steve

      Do you have a benchmark test for using Atom api? I’ve very interested in it performance gain, in terms of speed, then the traditional python classes. Thanks!

  • http://www.theroadtosiliconvalley.com/ Ernest Semerda

    Awesome! Thanks for sharing.

  • asmo

    Thanks for a useful tip! Helped me save about 40% of memory in a memory intensive analysis-

  • Aleksey Tulinov

    Take a look into bsddb, it can do hash table with dict-like access, on-disk storage and internal cache.

  • Pingback: Coder Read #20131119 – Let’s Code the Firefox | Coders Grid

  • Terrence Brannon

    Hello, you state “It’s not great for code maintenance, ” but do not link to any documentation as to why, nor do you justify it. Please do so.

    • http://benhoyt.com/ Ben Hoyt

      Because you end up specifying the field names twice, so it’s not DRY, and whenever you add or remove a field, you have to do so in multiple places. Note that namedtuple gets around this.

  • MvK

    Thank you very very much! I could use the same trick today to reduce a 16GB footprint to 9GB.

  • Piyush Kansal

    Good to know. Thanks for sharing this.

  • Pingback: This week in Python (2013 - 47) - Jworks.nl - Agile Software Development using Groovy and Grails | Jworks.nl - Agile Software Development using Groovy and Grails

  • kitsunde

    Is there a reason why you are using a list instead of a tuple if the attributes are fixed?

    • http://benhoyt.com/ Ben Hoyt

      No particular reason. __slots__ accepts an iterable or “sequence”, so either list or tuple is fine. Using a list for __slots__ is at least as common in the Python stdlib and tests. I guess I often think of a list as storing a bunch of the same kind of data (names of attributes in this case), whereas tuples often store a bunch of different things (like a namedtuple, or a struct in C).

      Read more about this thinking here: http://stackoverflow.com/questions/626759/‎

      • kitsunde

        Ah, thanks for pointing to the stdlib. It seems that even within the same module like multiprocessing it’s not consistent (even looking at it in full context as far as I can see):

        $ grep “__slots__” multiprocessing/managers.py
        __slots__ = (‘typeid’, ‘address’, ‘id’)
        __slots__ = ['value']

        I suppose I should direct my inquiry to the issue tracker and the core devs. :)

  • Pingback: Saving 9 GB of RAM with Python’s __slots__ | TextMagic

Previous post: