tko*_*zka 118 python performance dictionary object
在内存使用和CPU消耗方面,Python的效率更高 - 词典还是对象?
背景: 我必须将大量数据加载到Python中.我创建了一个只是一个字段容器的对象.创建4M实例并将它们放入字典大约需要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)
Run Code Online (Sandbox Code Playgroud)
字典(执行时间~12秒):
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
Run Code Online (Sandbox Code Playgroud)
问题: 我做错了什么或字典比对象更快?如果确实字典表现更好,有人可以解释为什么吗?
cod*_*ape 148
你试过用过__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)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
test_dict.py:
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
Run Code Online (Sandbox Code Playgroud)
test_namedtuple.py(2.6支持):
import collections
Obj = collections.namedtuple('Obj', 'i l')
all = {}
for i in range(1000000):
all[i] = Obj(i, [])
Run Code Online (Sandbox Code Playgroud)
运行基准测试(使用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__)
Run Code Online (Sandbox Code Playgroud)
使用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)
Run Code Online (Sandbox Code Playgroud)
所以是的(不是真的很惊讶),使用__slots__是性能优化.使用命名元组具有类似的性能__slots__.
Vin*_*jip 14
对象中的属性访问使用幕后的字典访问 - 因此通过使用属性访问,您将增加额外的开销.此外,在对象的情况下,由于例如额外的内存分配和代码执行(例如__init__方法),您会产生额外的开销.
在您的代码中,if o是一个Obj实例,o.attr相当于o.__dict__['attr']少量的额外开销.
你考虑过使用namedtuple吗?(链接为python 2.4/2.5)
它是表示结构化数据的新标准方式,它为您提供元组的性能和类的便利性.
与词典相比,它唯一的缺点是(像元组一样)它不会让你在创建后改变属性.
这是python 3.6.1的@hughdbrown答案的副本,我将计数增加了5倍并添加了一些代码以在每次运行结束时测试python进程的内存占用。
在downvoters有它之前,请注意,这种计算对象大小的方法不准确。
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()
Run Code Online (Sandbox Code Playgroud)
这些是我的结果
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
Run Code Online (Sandbox Code Playgroud)
我的结论是: