Python 字典与对象-哪个更有效?为什么?
在Python中,在内存使用和CPU消耗方面,字典还是对象更有效 背景: 我必须将大量数据加载到Python中。我创建了一个对象,它只是一个字段容器。创建400万个实例并将它们放入字典大约需要10分钟和~6GB内存。词典准备好后,一眨眼就可以查到它 示例: 为了检查性能,我编写了两个简单的程序,一个使用对象,另一个使用字典: 对象(执行时间~18秒): 字典(执行时间~12秒): 问题:Python 字典与对象-哪个更有效?为什么?,python,performance,dictionary,object,Python,Performance,Dictionary,Object,在Python中,在内存使用和CPU消耗方面,字典还是对象更有效 背景: 我必须将大量数据加载到Python中。我创建了一个对象,它只是一个字段容器。创建400万个实例并将它们放入字典大约需要10分钟和~6GB内存。词典准备好后,一眨眼就可以查到它 示例: 为了检查性能,我编写了两个简单的程序,一个使用对象,另一个使用字典: 对象(执行时间~18秒): 字典(执行时间~12秒): 问题: 是我做错了什么,还是字典比对象快?如果dictionary的性能确实更好,有人能解释一下原因吗?对象中的属性
是我做错了什么,还是字典比对象快?如果dictionary的性能确实更好,有人能解释一下原因吗?对象中的属性访问在幕后使用dictionary访问,因此使用属性访问会增加额外的开销。另外,在对象情况下,由于额外的内存分配和代码执行(例如
\uuuuu init\uuu
方法),您会产生额外的开销
在您的代码中,如果
o
是一个Obj
实例,o.attr
相当于o.\uuu dict\uuuu['attr']
,有少量额外开销。您尝试过使用\uu插槽吗
从:
默认情况下,新旧样式类的实例都有一个用于属性存储的字典。这会为实例变量很少的对象浪费空间。创建大量实例时,空间消耗可能会变得非常严重
可以通过在新样式的类定义中定义\uuuuuuuuuuuuuuuuuuuuuuuuuuuu
来覆盖默认值。\uuuuuuuuuuuuuuuuuuuuuuuu
声明获取一系列实例变量,并在每个实例中保留足够的空间来保存每个变量的值。节省空间是因为没有为每个实例创建\uuu dict\uu
那么这能节省时间和内存吗
在我的计算机上比较三种方法:
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)
测试对象:
class Obj(object):
def __init__(self, i):
self.i = i
self.l = []
all = {}
for i in range(1000000):
all[i] = Obj(i)
测试目录:
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
test_namedtuple.py(2.6中支持):
运行基准测试(使用CPython 2.5):
使用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)
所以,是的(这并不奇怪),使用\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
是性能优化。使用命名元组的性能与\uuu\uu\uu
类似
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
毫无疑问。
您有数据,没有其他属性(没有方法,什么都没有)。因此,您有一个数据容器(在本例中为字典)
我通常更喜欢从数据建模的角度来思考。如果存在一些巨大的性能问题,那么我可以放弃抽象中的某些东西,但必须有很好的理由。
编程就是管理复杂性,而维护正确的抽象通常是实现这种结果最有用的方法之一
关于物体速度较慢的原因,我认为您的测量不正确。
在for循环中执行的赋值太少,因此实例化dict(内在对象)和“自定义”对象所需的时间不同。虽然从语言的角度来看,它们是相同的,但它们的实现却截然不同。
在这之后,两者的分配时间应该几乎相同,因为最终成员都保存在字典中。您考虑过使用一个新的分配时间吗?()
它是表示结构化数据的新的标准方式,为您提供了元组的性能和类的便利性
与字典相比,它唯一的缺点是(像元组一样),它不能让您在创建后更改属性。这是一份针对python 3.6.1的@hughdbrown answer的副本,我将计数增加了5倍,并在每次运行结束时添加了一些代码来测试python进程的内存占用
在下议院投票人开始之前,请注意,这种计算物体大小的方法是不准确的
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 profile_dict_of_nt():
return [NT(i=i, l=makeL(i)) for i in range(ITER_COUNT)]
@timeit
def profile_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)]
profile_dict_of_nt()
profile_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
我的结论是:
插槽具有最佳的内存占用,并且速度合理
dicts是最快的,但是使用的内存最多
如果数据结构不应该包含引用周期,那么还有另一种方法可以减少内存使用
让我们比较两个类:
class DataItem:
__slots__ = ('name', 'age', 'address')
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
及
这是因为基于structclass
的类不支持循环垃圾收集,在这种情况下不需要循环垃圾收集
与基于\uuuuuuuuuuuuuuuuuuuuuuuuuu>的类相比,还有一个优势:您可以添加额外的属性:
>>> DataItem3 = structclass('DataItem', 'name age address', usedict=True)
>>> inst3 = DataItem3('Mike', 10, 'Cherry Street 15')
>>> inst3.hobby = ['drawing', 'singing']
>>> print(inst3)
>>> print(sizeof(inst3), 'has dict:', bool(inst3.__dict__))
DataItem(name='Mike', age=10, address='Cherry Street 15', **{'hobby': ['drawing', 'singing']})
48 has dict: True
下面是我对@Jarrod Chesney非常好的脚本的测试运行。
为了进行比较,我还对python2运行了它,并将“range”替换为“xrange”
出于好奇,我还使用OrderedDict(ordict)添加了类似的测试以进行比较
Python 3.6.9:
Time Taken = 0:00:04.971369, profile_dict_of_nt, Size = 944.27
Time Taken = 0:00:05.743104, profile_list_of_nt, Size = 1,066.93
Time Taken = 0:00:02.524507, profile_dict_of_dict, Size = 1,920.35
Time Taken = 0:00:02.123801, profile_list_of_dict, Size = 1,760.9
Time Taken = 0:00:05.374294, profile_dict_of_obj, Size = 1,532.12
Time Taken = 0:00:04.517245, profile_list_of_obj, Size = 1,441.04
Time Taken = 0:00:04.590298, profile_dict_of_slot, Size = 1,030.09
Time Taken = 0:00:04.197425, profile_list_of_slot, Size = 870.67
Time Taken = 0:00:08.833653, profile_ordict_of_ordict, Size = 3,045.52
Time Taken = 0:00:11.539006, profile_list_of_ordict, Size = 2,722.34
Time Taken = 0:00:06.428105, profile_ordict_of_obj, Size = 1,799.29
Time Taken = 0:00:05.559248, profile_ordict_of_slot, Size = 1,257.75
Python 2.7.15+:
Time Taken = 0:00:05.193900, profile_dict_of_nt, Size = 906.0
Time Taken = 0:00:05.860978, profile_list_of_nt, Size = 1,177.0
Time Taken = 0:00:02.370905, profile_dict_of_dict, Size = 2,228.0
Time Taken = 0:00:02.100117, profile_list_of_dict, Size = 2,036.0
Time Taken = 0:00:08.353666, profile_dict_of_obj, Size = 2,493.0
Time Taken = 0:00:07.441747, profile_list_of_obj, Size = 2,337.0
Time Taken = 0:00:06.118018, profile_dict_of_slot, Size = 1,117.0
Time Taken = 0:00:04.654888, profile_list_of_slot, Size = 964.0
Time Taken = 0:00:59.576874, profile_ordict_of_ordict, Size = 7,427.0
Time Taken = 0:10:25.679784, profile_list_of_ordict, Size = 11,305.0
Time Taken = 0:05:47.289230, profile_ordict_of_obj, Size = 11,477.0
Time Taken = 0:00:51.485756, profile_ordict_of_slot, Size = 11,193.0
因此,在这两个主要版本中,@Jarrod Chesney的结论看起来仍然不错。你测试过这个吗<代码>o.uu dict_uu[“attr”]
是一个具有额外开销的代码,需要额外的字节码运算;obj.attr更快。(当然,属性访问不会比订阅访问慢——这是一个关键的、高度优化的代码路径。)显然,如果你真的执行o.uu dict_uu[“attr”]它会慢一些——我只是想说它等同于订阅访问,而不是说它是以那种方式实现的。我想从我的措辞看不清楚。我还提到了其他因素,如内存分配、构造函数调用时间等。11年后,python3的最新版本仍然如此吗?在生成这样的大序列时,您真的应该使用xrange而不是range。当然,由于您要处理的是几秒钟的执行时间,因此不会产生太大的差异
class DataItem:
__slots__ = ('name', 'age', 'address')
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
$ pip install recordclass
>>> from recordclass import structclass
>>> DataItem2 = structclass('DataItem', 'name age address')
>>> inst = DataItem('Mike', 10, 'Cherry Street 15')
>>> inst2 = DataItem2('Mike', 10, 'Cherry Street 15')
>>> print(inst2)
>>> print(sys.getsizeof(inst), sys.getsizeof(inst2))
DataItem(name='Mike', age=10, address='Cherry Street 15')
64 40
>>> DataItem3 = structclass('DataItem', 'name age address', usedict=True)
>>> inst3 = DataItem3('Mike', 10, 'Cherry Street 15')
>>> inst3.hobby = ['drawing', 'singing']
>>> print(inst3)
>>> print(sizeof(inst3), 'has dict:', bool(inst3.__dict__))
DataItem(name='Mike', age=10, address='Cherry Street 15', **{'hobby': ['drawing', 'singing']})
48 has dict: True
Time Taken = 0:00:04.971369, profile_dict_of_nt, Size = 944.27
Time Taken = 0:00:05.743104, profile_list_of_nt, Size = 1,066.93
Time Taken = 0:00:02.524507, profile_dict_of_dict, Size = 1,920.35
Time Taken = 0:00:02.123801, profile_list_of_dict, Size = 1,760.9
Time Taken = 0:00:05.374294, profile_dict_of_obj, Size = 1,532.12
Time Taken = 0:00:04.517245, profile_list_of_obj, Size = 1,441.04
Time Taken = 0:00:04.590298, profile_dict_of_slot, Size = 1,030.09
Time Taken = 0:00:04.197425, profile_list_of_slot, Size = 870.67
Time Taken = 0:00:08.833653, profile_ordict_of_ordict, Size = 3,045.52
Time Taken = 0:00:11.539006, profile_list_of_ordict, Size = 2,722.34
Time Taken = 0:00:06.428105, profile_ordict_of_obj, Size = 1,799.29
Time Taken = 0:00:05.559248, profile_ordict_of_slot, Size = 1,257.75
Time Taken = 0:00:05.193900, profile_dict_of_nt, Size = 906.0
Time Taken = 0:00:05.860978, profile_list_of_nt, Size = 1,177.0
Time Taken = 0:00:02.370905, profile_dict_of_dict, Size = 2,228.0
Time Taken = 0:00:02.100117, profile_list_of_dict, Size = 2,036.0
Time Taken = 0:00:08.353666, profile_dict_of_obj, Size = 2,493.0
Time Taken = 0:00:07.441747, profile_list_of_obj, Size = 2,337.0
Time Taken = 0:00:06.118018, profile_dict_of_slot, Size = 1,117.0
Time Taken = 0:00:04.654888, profile_list_of_slot, Size = 964.0
Time Taken = 0:00:59.576874, profile_ordict_of_ordict, Size = 7,427.0
Time Taken = 0:10:25.679784, profile_list_of_ordict, Size = 11,305.0
Time Taken = 0:05:47.289230, profile_ordict_of_obj, Size = 11,477.0
Time Taken = 0:00:51.485756, profile_ordict_of_slot, Size = 11,193.0