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
我的结论是:
- 插槽具有最佳的内存占用,并且速度合理。
- 字典是最快的,但使用最多的内存。