views:

608

answers:

5

What is more efficient in Python in terms of memory usage and CPU consumption - Dictionary or Object?

Background: I have to load huge amount of data into Python. I created an object that is just a field container. Creating 4M instances and putting them into a dictionary took about 10 minutes and ~6GB of memory. After dictionary is ready, accessing it is a blink of an eye.

Example: To check the performance I wrote two simple programs that do the same - one is using objects, other dictionary:

Object (execution time ~18sec):

class Obj(object):
  def __init__(self, i):
    self.i = i
    self.l = []
all = {}
for i in range(1000000):
  all[i] = Obj(i)

Dictionary (execution time ~12sec):

all = {}
for i in range(1000000):
  o = {}
  o['i'] = i
  o['l'] = []
  all[i] = o

Question: Am I doing something wrong or dictionary is just faster than object? If indeed dictionary performs better, can somebody explain why?

+7  A: 

Attribute access in an object uses dictionary access behind the scenes - so by using attribute access you are adding extra overhead. Plus in the object case, you are incurring additional overhead because of e.g. additional memory allocations and code execution (e.g. of the __init__ method).

In your code, if o is an Obj instance, o.attr is equivalent to o.__dict__['attr'] with a small amount of extra overhead.

Vinay Sajip
Eh? Why the downvote?
Vinay Sajip
Did you test this? `o.__dict__["attr"]` is the one with extra overhead, taking an extra bytecode op; obj.attr is faster. (Of course attribute access isn't going to be slower than subscription access--it's a critical, heavily optimized code path.)
Glenn Maynard
Obviously if you actually **do** o.__dict__["attr"] it will be slower - I only meant to say that it was equivalent to that, not that it was implemented exactly in that way. I guess it's not clear from my wording. I also mentioned other factors such as memory allocations, constructor call time etc.
Vinay Sajip
+13  A: 

Have you tried using __slots__?

From the documentation http://docs.python.org/reference/datamodel.html#slots:

"By default, instances of both old and new-style classes have a dictionary for attribute storage. This wastes space for objects having very few instance variables. The space consumption can become acute when creating large numbers of instances.

The default can be overridden by defining __slots__ in a new-style class definition. The __slots__ declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because __dict__ is not created for each instance."

So does this save time as well as memory?

Comparing the three approaches on my computer:

test_slots.py:

class Obj(object):
  __slots__ = ('i', 'l')
  def __init__(self, i):
    self.i = i
    self.l = []
all = {}
for i in range(1000000):
  all[i] = Obj(i)

test_obj.py:

class Obj(object):
  def __init__(self, i):
    self.i = i
    self.l = []
all = {}
for i in range(1000000):
  all[i] = Obj(i)

test_dict.py:

all = {}
for i in range(1000000):
  o = {}
  o['i'] = i
  o['l'] = []
  all[i] = o

test_namedtuple.py (supported in 2.6):

import collections

Obj = collections.namedtuple('Obj', 'i l')

all = {}
for i in range(1000000):
  all[i] = Obj(i, [])

Run benchmark (using CPython 2.5):

$ lshw | grep product | head -n 1
          product: Intel(R) Pentium(R) M processor 1.60GHz
$ python --version
Python 2.5
$ time python test_obj.py && time python test_dict.py && time python test_slots.py 

real    0m27.398s (using 'normal' object)
real    0m16.747s (using __dict__)
real    0m11.777s (using __slots__)

Using CPython 2.6.2, including the named tuple test:

$ python --version
Python 2.6.2
$ time python test_obj.py && time python test_dict.py && time python test_slots.py && time python test_namedtuple.py 

real    0m27.197s (using 'normal' object)
real    0m17.657s (using __dict__)
real    0m12.249s (using __slots__)
real    0m12.262s (using namedtuple)

So yes (not really a surprise), using __slots__ is a performance optimization. Using a named tuple has similar performance to __slots__.

codeape
That is great - thanks! I've tried the same on my machine - object with __slots__ is the most efficient approach (I got ~7sec).
tkokoszka
There are also named tuples, http://docs.python.org/library/collections.html#collections.namedtuple , a class factory for objects with slots. It definitely neater and maybe even more optimized.
THC4k
I tested named tuples as well, and updated the answer with the results.
codeape
Indeed the best answer
+2  A: 
try:
    import psyco
    psyco.full()
except:
    pass

import datetime
class Obj(object):
  def __init__(self, i):
    self.i = i
    self.l = []

s = datetime.datetime.now()
all = dict((i, {'i':i, 'l':[]}) for i in range(1000000))
e = datetime.datetime.now()
print "Dict Elapsed: ", (e - s)

all = dict()

s = datetime.datetime.now()
all = dict((i, Obj(i)) for i in range(1000000))
e = datetime.datetime.now()
print "Object Elapsed: ", (e - s)

Results (without slots):

C:\Users\hughdbrown\Documents\python\StackOverflow>python 1336791.py
Dict Elapsed:  0:00:06.307000
Object Elapsed:  0:00:10.437000

Results (with slots)

C:\Users\hughdbrown\Documents\python\StackOverflow>python 1336791.py
Dict Elapsed:  0:00:06.588000
Object Elapsed:  0:00:04.541000

You can also cut the time down by another 20% by using xrange() and list comprehensions:

all = [{'i':i, 'l':[]} for i in xrange(1000000)]

all = [Obj(i) for i in xrange(1000000)]

Results are:

C:\Users\hughdbrown\Documents\python\StackOverflow>python 1336791.py
Dict Elapsed:  0:00:05.348000
Object Elapsed:  0:00:03.481000
hughdbrown
A: 

There is no question.
You have data, with no other attributes (no methods, nothing). Hence you have a data container (in this case, a dictionary).

I usually prefer to think in terms of data modeling. If there is some huge performance issue, then I can give up something in the abstraction, but only with very good reasons.
Programming is all about managing complexity, and the maintaining the correct abstraction is very often one of the most useful way to achieve such result.

About the reasons an object is slower, I think your measurement is not correct.
You are performing too little assignments inside the for loop, and therefore what you see there is the different time necessary to instantiate a dict (intrinsic object) and a "custom" object. Although from the language perspective they are the same, they have quite a different implementation.
After that, the assignment time should be almost the same for both, as in the end members are maintained inside a dictionary.

Roberto Liffredo
A: 

Have you considered using a namedtuple? (link for python 2.4/2.5)

It's the new standard way of representing structured data that gives you the performance of a tuple and the convenience of a class.

It's only downside is that it doesn't give you the ability to change attributes after creation (like tuples).

John Fouhy