
Saving 9 GB of RAM with Python's __slots__ - benhoyt
http://tech.oyster.com/save-ram-with-python-slots/
======
_wmd
Worth pointing out that on PyPy you effectively get this for free..
[http://morepypy.blogspot.co.uk/2010/11/efficiently-
implement...](http://morepypy.blogspot.co.uk/2010/11/efficiently-implementing-
python-objects.html)

~~~
PythonicAlpha
PyPy is not an option everywhere. Since as much I know, many, many
enhancements and libraries are just not available for PyPy but just for plain
old CPython.

PyPy might be an interesting project with much potential, but there seams to
me a long way until it can be an one-shot replacement for CPyton.

------
PythonicAlpha
That's the cost of being a dynamic language. Since Python objects can be
dynamically enhanced everywhere (also from inheriting classes and even from
outside of the class) it needs dictionaries. But those can be very memory
inefficient, specially on modern 64bit Hardware. One dict can easily take 1-2k
for very few stored attributes (size can even depend on actual names used,
because of the nature of dicts). So when it comes to millions of object
instances, it is better to use __slots__ but those come with a cost: Those
objects are not enhance-able any more. You have to know all attributes of the
objects in advance. So you should only use it on objects that are really used
a lot or are really simple.

~~~
brandonbloom
> That's the cost of being a dynamic language.

No, this is a cost of this particular style of dynamic object model. Not all
dynamic languages are dynamic in this way.

> Those objects are not enhance-able any more.

I don't see any reason why Python can't do what Clojure's defrecord does:
Provide fixed fields for pre-declared slots, while still using a dictionary
for extensions. It has been a while since I've used python, but I'm almost
certain that there is some __special__ magic that can make this work with
relative ease.

It's also worth pointing out that most modern JITs, like V8 or PyPy, can
automatically detect "hidden classes" like this and optimize these objects to
pack such static fields.

~~~
gsnedders
"modern JITs" aren't so modern at all. All that work was originally done on
Smalltalk in the 1980s — it's also entirely tangential to JITing compilers, as
it can easily be done with interpreters too, so it's not even a cost of this
particular style of dynamic object model — it's a cost of this implementation
strategy of this particular style of dynamic object model. The fact that PyPy
manages fine shows it is not the language, or any model to which it
subscribes, that is at fault.

------
austinz
I did something similar for a batch log processing system I wrote in Python
some time ago. All the log messages could be classified as representing one of
a few dozen 'packet' types, each represented by an object instance (so I could
do some additional processing later), so predefining each type's fixed sets of
fields using slots noticeably decreased memory usage. Of course, it was the
first time I had ever done anything like that in Python, so I may have been
doing it wrong...

Anyways, definitely a good short read, thanks for posting!

------
omegote
Mmm I don't quite get why sys.getsizeof is reporting a bigger size in the
slotted class, it should be the other way around according to that post.

Test code at: [http://codepad.org/wlb53BLf](http://codepad.org/wlb53BLf) not
sure if I'm missing something...

~~~
benhoyt
Per the Python 3 docs (for some reason not in the Python 2 docs, but the same
holds): "Only the memory consumption directly attributed to the object is
accounted for, not the memory consumption of objects it refers to."

Most of the space for the NonSlotted version is in the __dict__, and if you
print the size of ni.__dict__ you'll probably get a couple of hundred bytes.

There are better, recursive ways to get the real size of a Python object in
memory, for example see:
[http://pythonhosted.org/Pympler/asizeof.html#asizeof](http://pythonhosted.org/Pympler/asizeof.html#asizeof)

~~~
omegote
Nice, just used pympler.asizeof and it reported that the slotted version has
about 22% of the size of the non-slotted version.

------
asmosoinio
Useful tip. Anecdotally this helped me save 40% of memory on some data I need
to store in memory for analysis: Used to be about 1KB per object, after adding
__slots__ it came down to 590 bytes.

------
pmiller2
Using __slots__ is not really the same as using a namedtuple, because
namedtuples are immutable.

~~~
burntsushi
The OP didn't say it was the same. It said they were similar. And they
certainly are.

Although, there may be performance differences between `namedtuple` and
`__slots__`. Particularly _access_ time. This SO post elaborates.[1]

[1] - [http://stackoverflow.com/questions/2646157/what-is-the-
faste...](http://stackoverflow.com/questions/2646157/what-is-the-fastest-to-
access-struct-like-object-in-python)

------
mixmastamyk
Thanks, great article. I've used Python for years, but this was a remaining
dark corner I hadn't got to yet. Now, off to the next.

------
stcredzero
Basically, going back to Smalltalk's memory model. It also becomes much easier
to JIT optimized machine code for such objects.

~~~
rguillebert
PyPy does the same thing as using __slots__ on CPython automatically, no need
to use __slots__ to take advantage of the JIT.

~~~
stcredzero
I didn't say that __slots__ makes JIT possible. However, it does make writing
one easier. (Also makes writing a faster one easier.)

EDIT: Is it the new modus operandi on HN: If a statement isn't seemingly 100%
in support of your pet language, automatically read the statement in the
dimmest and narrowest way possible?

~~~
sgrove
Just think of it as there was some confusion and possible ambiguity, and your
clarification has cleared it up for anyone interested in the subject but not
yet knowledgeable enough. Someone can skim through and have their mental model
corrected slightly now - a very nice thing!

------
kevinburke
Probably missing a lot of context here, but wondering why you wouldn't use
something like nginx or squid for serving static content, as they are designed
for this kind of use case.

~~~
benhoyt
Good question -- however, it's not completely static content. The hotel
reviews and photos are more or less static (updated only on deployment),
however a fair number of the features of the site are dynamic: user accounts,
real-time pricing, search, recently-viewed hotels, etc.

See also my comment on reddit about design decisions:
[http://www.reddit.com/r/programming/comments/1qu5ai/saving_9...](http://www.reddit.com/r/programming/comments/1qu5ai/saving_9_gb_of_ram_with_one_line_of_code_using/cdgjxom)

~~~
zhemao
Have you considered using something like Memcached or Redis then? There'd be
some overhead sending data over a local TCP connection, but I think it would
be a lot more memory-efficient.

------
simgidacav
Extra-nice thing about this feature: it can be enabled and disabled, for a
class, with a very little effort. So you can check correctness first, and
optimize later.

Does that fuck up? Rinse and repeat.

------
cmhamill
Does anyone know if there's any similar ability in Perl 5?

~~~
xb95
Yup. It's called 'fields':
[http://perldoc.perl.org/fields.html](http://perldoc.perl.org/fields.html)

I echo what they said in that post, though: don't prematurely optimize. If you
find you have tons of objects and need the RAM or you're actually paying a
premium for hash accesses, then fields can save you some effort... but if
you've a small use case, don't bother.

------
RaphiePS
Does anyone know what the code is "compiled" into, if not a hash table?

~~~
Mithaldu
I'm not entirely sure, but based on my experience with OO in Perl i guess that
it simply uses an array in a special attribute, instead of putting the various
attributes into dict keys on the actual object. Possibly it even uses some
kind of inside-out implementation where the arrays are stored via closure in
some other scope and only visible to accessor methods.

------
pdknsk
Why run Python on Windows?

~~~
mixmastamyk
Rather, why not?

Works perfectly well, and empowers one to escape from it at a later date if
necessary.

