字典与对象 - 哪个更高效,为什么?
在Python中,字典和对象在内存使用和CPU消耗方面,哪个更高效呢?
背景:
我需要把大量数据加载到Python中。我创建了一个对象,用来存放字段。创建400万个实例并把它们放进一个字典里,花了大约10分钟,使用了大约6GB的内存。字典准备好后,访问它的速度非常快。
示例:
为了检查性能,我写了两个简单的程序,它们做的事情是一样的,一个使用对象,另一个使用字典:
对象(执行时间大约18秒):
class Obj(object):
def __init__(self, i):
self.i = i
self.l = []
all = {}
for i in range(1000000):
all[i] = Obj(i)
字典(执行时间大约12秒):
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
问题:
我是不是做错了什么,还是说字典就是比对象快?如果字典确实表现更好,能有人解释一下为什么吗?
8 个回答
你有没有考虑过使用命名元组?(这是针对Python 2.4/2.5的链接)
命名元组是一种新的标准方式来表示结构化数据,它结合了元组的高效性能和类的便利性。
它和字典相比唯一的缺点是(就像元组一样),创建后不能更改属性。
在一个对象中访问属性,其实是在后台使用字典的方式来实现的。所以,当你用属性访问时,其实是在增加一些额外的开销。而且在对象的情况下,还会因为一些额外的内存分配和代码执行(比如执行 __init__
方法)而产生更多的开销。
在你的代码中,如果 o
是一个 Obj
的实例,那么 o.attr
和 o.__dict__['attr']
是等价的,只是多了一点点额外的开销。
你有没有试过用 __slots__
呢?
根据文档:
默认情况下,无论是旧式类还是新式类的实例,都会有一个字典来存储属性。这对于那些只有很少实例变量的对象来说,会浪费空间。当创建大量实例时,这种空间浪费会变得很严重。
通过在新式类定义中定义
__slots__
,可以覆盖这个默认设置。__slots__
声明了一系列实例变量,并为每个实例保留足够的空间来存储每个变量的值。这样可以节省空间,因为每个实例不会创建__dict__
。
那么,这样做是节省时间还是内存呢?
在我的电脑上比较了三种方法:
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(在2.6中支持):
import collections
Obj = collections.namedtuple('Obj', 'i l')
all = {}
for i in range(1000000):
all[i] = Obj(i, [])
运行基准测试(使用 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__)
使用 CPython 2.6.2,包括命名元组测试:
$ 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)
所以是的(这并不意外),使用 __slots__
是一种性能优化。使用命名元组的性能与 __slots__
相似。