字典与对象 - 哪个更有效,为什么?

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__.

  • 还有名为元组的http://docs.python.org/library/collections.html#collections.namedtuple,一个带有插槽的对象的类工厂.它绝对更整洁,甚至可能更加优化. (5认同)
  • 太棒了 - 谢谢!我在我的机器上尝试过相同的东西 - 使用__slots__的对象是最有效的方法(我得到了~7秒). (2认同)

Vin*_*jip 14

对象中的属性访问使用幕后的字典访问 - 因此通过使用属性访问,您将增加额外的开销.此外,在对象的情况下,由于例如额外的内存分配和代码执行(例如__init__方法),您会产生额外的开销.

在您的代码中,if o是一个Obj实例,o.attr相当于o.__dict__['attr']少量的额外开销.

  • 显然,如果您实际上**做o .__ dict __ [“ attr”]会比较慢-我只是说这与它等效,而不是完全按照这种方式实现。我想我的措辞不清楚。我还提到了其他因素,例如内存分配,构造函数调用时间等。 (2认同)
  • 11 年后,最新版本的 python3 仍然是这种情况吗? (2认同)

Joh*_*uhy 8

你考虑过使用namedtuple吗?(链接为python 2.4/2.5)

它是表示结构化数据的新标准方式,它为您提供元组的性能和类的便利性.

与词典相比,它唯一的缺点是(像元组一样)它不会让你在创建后改变属性.


Jar*_*ney 8

这是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)

我的结论是:

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