Dictionary vs Object – 哪个更有效率,为什么?

在内存使用和CPU消耗方面,什么是更高效的Python – 字典或对象?

背景:我必须将大量的数据加载到Python中。 我创build了一个只是一个字段容器的对象。 创build4M实例并将其放入字典需要大约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 

问题:我做错了什么或字典比对象快? 如果确实字典performance更好,有人可以解释为什么?

你尝试过使用__slots__吗?

从文档http://docs.python.org/reference/datamodel.html#slots

“默认情况下,旧式和新式类的实例都有一个属性存储的字典,这会浪费空间用于实例variables非常less的对象,在创build大量实例时,空间消耗会变得非常尖锐。

可以通过在新风格类定义中定义__slots__来覆盖默认值。 __slots__声明采用一系列实例variables,并在每个实例中保留足够的空间来为每个variables保存一个值。 空间被保存,因为__dict__不是为每个实例创build的。“

那么这是否节省时间以及内存呢?

比较我的电脑上的三种方法:

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, []) 

运行基准testing(使用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,包括命名的元组testing:

 $ 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__具有相似的性能。

对象的属性访问在后台使用字典访问 – 所以通过使用属性访问,您将增加额外的开销。 另外在对象情况下,由于例如额外的内存分配和代码执行(例如, __init__方法),您将招致额外的开销。

在你的代码中,如果o是一个Obj实例, o.attr相当于o.__dict__['attr']带有less量的额外开销。

你有没有考虑使用namedtuple ? ( 链接为Python 2.4 / 2.5 )

这是表示结构化数据的新标准方法,可以为您提供元组的性能和类的便利性。

与字典相比,这只是一个缺点,就是(像元组),它不能让你在创build后改变属性。

 from datetime import datetime ITER_COUNT = 1000 * 1000 def timeit(method): def timed(*args, **kw): s = datetime.now() result = method(*args, **kw) e = datetime.now() print method.__name__, '(%r, %r)' % (args, kw), e - s return result return timed class Obj(object): def __init__(self, i): self.i = i self.l = [] class SlotObj(object): __slots__ = ('i', 'l') def __init__(self, i): self.i = i self.l = [] @timeit def profile_dict_of_dict(): return dict((i, {'i': i, 'l': []}) for i in xrange(ITER_COUNT)) @timeit def profile_list_of_dict(): return [{'i': i, 'l': []} for i in xrange(ITER_COUNT)] @timeit def profile_dict_of_obj(): return dict((i, Obj(i)) for i in xrange(ITER_COUNT)) @timeit def profile_list_of_obj(): return [Obj(i) for i in xrange(ITER_COUNT)] @timeit def profile_dict_of_slotobj(): return dict((i, SlotObj(i)) for i in xrange(ITER_COUNT)) @timeit def profile_list_of_slotobj(): return [SlotObj(i) for i in xrange(ITER_COUNT)] if __name__ == '__main__': profile_dict_of_dict() profile_list_of_dict() profile_dict_of_obj() profile_list_of_obj() profile_dict_of_slotobj() profile_list_of_slotobj() 

结果:

 hbrown@hbrown-lpt:~$ python ~/Dropbox/src/StackOverflow/1336791.py profile_dict_of_dict ((), {}) 0:00:08.228094 profile_list_of_dict ((), {}) 0:00:06.040870 profile_dict_of_obj ((), {}) 0:00:11.481681 profile_list_of_obj ((), {}) 0:00:10.893125 profile_dict_of_slotobj ((), {}) 0:00:06.381897 profile_list_of_slotobj ((), {}) 0:00:05.860749 

没有问题。
你有数据,没有其他的属性(没有方法,没有)。 因此,你有一个数据容器(在这种情况下,一个字典)。

我通常更喜欢用数据build模来思考。 如果存在一些巨大的性能问题,那么我可以在抽象中放弃一些东西,但只有很好的理由。
编程是关于pipe理复杂性的,而维护正确的抽象往往是实现这种结果最有用的方法之一。

关于一个对象较慢的原因 ,我认为你的测量是不正确的。
您在for循环内部执行的任务太less,因此您在那里看到的是实例化dict(内部对象)和“自定义”对象所需的不同时间。 尽pipe从语言angular度来看它们是一样的,但它们的实现却有很大不同。
在那之后,两人的任务时间应该是几乎相同的,因为最终的成员是在字典里面维护的。

这里是python 3.6.1的@hughdbrown答案的副本,我已经将count数值扩大了5倍,并在每次运行结束时添加了一些代码来testingpython进程的内存占用情况。

在此之前,请注意这种计算物体大小的方法是不准确的。

 from datetime import datetime import os import psutil process = psutil.Process(os.getpid()) ITER_COUNT = 1000 * 1000 * 5 RESULT=None def makeL(i): # Use this line to negate the effect of the strings on the test # return "Python is smart and will only create one string with this line" # Use this if you want to see the difference with 5 million unique strings return "This is a sample string %s" % i def timeit(method): def timed(*args, **kw): global RESULT s = datetime.now() RESULT = method(*args, **kw) e = datetime.now() sizeMb = process.memory_info().rss / 1024 / 1024 sizeMbStr = "{0:,}".format(round(sizeMb, 2)) print('Time Taken = %s, \t%s, \tSize = %s' % (e - s, method.__name__, sizeMbStr)) return timed class Obj(object): def __init__(self, i): self.i = i self.l = makeL(i) class SlotObj(object): __slots__ = ('i', 'l') def __init__(self, i): self.i = i self.l = makeL(i) from collections import namedtuple NT = namedtuple("NT", ["i", 'l']) @timeit def provile_dict_of_nt(): return [NT(i=i, l=makeL(i)) for i in range(ITER_COUNT)] @timeit def provile_list_of_nt(): return dict((i, NT(i=i, l=makeL(i))) for i in range(ITER_COUNT)) @timeit def profile_dict_of_dict(): return dict((i, {'i': i, 'l': makeL(i)}) for i in range(ITER_COUNT)) @timeit def profile_list_of_dict(): return [{'i': i, 'l': makeL(i)} for i in range(ITER_COUNT)] @timeit def profile_dict_of_obj(): return dict((i, Obj(i)) for i in range(ITER_COUNT)) @timeit def profile_list_of_obj(): return [Obj(i) for i in range(ITER_COUNT)] @timeit def profile_dict_of_slot(): return dict((i, SlotObj(i)) for i in range(ITER_COUNT)) @timeit def profile_list_of_slot(): return [SlotObj(i) for i in range(ITER_COUNT)] provile_dict_of_nt() provile_list_of_nt() profile_dict_of_dict() profile_list_of_dict() profile_dict_of_obj() profile_list_of_obj() profile_dict_of_slot() profile_list_of_slot() 

这些是我的结果

 Time Taken = 0:00:07.018720, provile_dict_of_nt, Size = 951.83 Time Taken = 0:00:07.716197, provile_list_of_nt, Size = 1,084.75 Time Taken = 0:00:03.237139, profile_dict_of_dict, Size = 1,926.29 Time Taken = 0:00:02.770469, profile_list_of_dict, Size = 1,778.58 Time Taken = 0:00:07.961045, profile_dict_of_obj, Size = 1,537.64 Time Taken = 0:00:05.899573, profile_list_of_obj, Size = 1,458.05 Time Taken = 0:00:06.567684, profile_dict_of_slot, Size = 1,035.65 Time Taken = 0:00:04.925101, profile_list_of_slot, Size = 887.49 

我的结论是:

  1. 插槽具有最佳的内存占用,并且速度合理。
  2. 字典是最快的,但使用最多的内存。