什么是最快(访问)类似于Python的结构对象?

我正在优化一些主要瓶颈正在运行的代码,并访问一个非常大的类结构对象列表。 目前我正在使用namedtuples,为了可读性。 但是一些使用“timeit”的快速基准testing表明,在性能是一个因素的情况下,这确实是一个错误的方法:

用a,b,c命名元组:

>>> timeit("z = ac", "from __main__ import a") 0.38655471766332994 

使用__slots__类,与a,b,c:

 >>> timeit("z = bc", "from __main__ import b") 0.14527461047146062 

带键a,b,c的字典:

 >>> timeit("z = c['c']", "from __main__ import c") 0.11588272541098377 

带三个值的元组,使用一个常量键:

 >>> timeit("z = d[2]", "from __main__ import d") 0.11106188992948773 

列出三个值,使用一个常量键:

 >>> timeit("z = e[2]", "from __main__ import e") 0.086038238242508669 

具有三个值的元组,使用本地键:

 >>> timeit("z = d[key]", "from __main__ import d, key") 0.11187358437882722 

使用本地密钥列出三个值:

 >>> timeit("z = e[key]", "from __main__ import e, key") 0.088604143037173344 

首先,有没有什么关于这些小时间testing,使它们失效? 我跑了几次,以确保没有随机系统事件抛出,结果几乎相同。

看起来,字典提供了性能和可读性之间的最佳平衡,class级排在第二位。 这是不幸的,因为,为了我的目的,我也需要这个对象是序列化的; 因此我select了named。

列表要快得多,但是常数键是不可维护的; 我不得不创build一堆索引常量,即KEY_1 = 1,KEY_2 = 2等,这也是不理想的。

我是否坚持这些select,还是有其他select,我已经错过了?

有一点要记住的是namedtuples是作为元组进行优化的。 如果您将访问者更改a[2]而不是ac ,则会看到与元组类似的性能。 原因是名称访问者正在有效地转化为对自己[idx]的调用,因此同时支付索引名称查找价格。

如果你的使用模式是按名称访问是通用的,但是以元组的forms访问的话,你可以写一个相当于namedtuple的命令,这个命令的作用相反:推迟索引查找以访问名字。 但是,您将在索引查找中支付价格。 例如这里有一个快速的实现:

 def makestruct(name, fields): fields = fields.split() import textwrap template = textwrap.dedent("""\ class {name}(object): __slots__ = {fields!r} def __init__(self, {args}): {self_fields} = {args} def __getitem__(self, idx): return getattr(self, fields[idx]) """).format( name=name, fields=fields, args=','.join(fields), self_fields=','.join('self.' + f for f in fields)) d = {'fields': fields} exec template in d return d[name] 

但是,当__getitem__必须被调用时,时序非常糟糕:

 namedtuple.a : 0.473686933517 namedtuple[0] : 0.180409193039 struct.a : 0.180846214294 struct[0] : 1.32191514969 

即与用于属性访问的__slots__类相同的性能(不出所料 – 就是这样),但是由于基于索引的访问中的双重查找而导致巨大的处罚。 (值得注意的是__slots__实际上并没有帮助很多,它节省了内存,但是没有它们的访问时间大致相同。)

第三个select是复制数据,例如。 从列表中获取子类并将其存储在属性和列表数据中。 但是,您实际上并没有获得与列表相同的性能。 在子类化(引入对纯python重载的检查)方面速度很快。 因此,在这种情况下,struct [0]仍然需要大约0.5s(原始列表为0.18),并且内存使用量加倍,所以这可能不值得。

这个问题是相当老的(互联网时间),所以我想我会尝试复制今天的testing,无论是与常规CPython(2.7.6),并与pypy(2.2.1),看看如何比较各种方法。 (我也添加了一个索引查找命名的元组。

这是一个微小的基准,所以YMMV,但pypy似乎加快命名元组访问30倍,而CPython(而字典访问只加快了3倍)。

 from collections import namedtuple STest = namedtuple("TEST", "abc") a = STest(a=1,b=2,c=3) class Test(object): __slots__ = ["a","b","c"] a=1 b=2 c=3 b = Test() c = {'a':1, 'b':2, 'c':3} d = (1,2,3) e = [1,2,3] f = (1,2,3) g = [1,2,3] key = 2 if __name__ == '__main__': from timeit import timeit print("Named tuple with a, b, c:") print(timeit("z = ac", "from __main__ import a")) print("Named tuple, using index:") print(timeit("z = a[2]", "from __main__ import a")) print("Class using __slots__, with a, b, c:") print(timeit("z = bc", "from __main__ import b")) print("Dictionary with keys a, b, c:") print(timeit("z = c['c']", "from __main__ import c")) print("Tuple with three values, using a constant key:") print(timeit("z = d[2]", "from __main__ import d")) print("List with three values, using a constant key:") print(timeit("z = e[2]", "from __main__ import e")) print("Tuple with three values, using a local key:") print(timeit("z = d[key]", "from __main__ import d, key")) print("List with three values, using a local key:") print(timeit("z = e[key]", "from __main__ import e, key")) 

Python结果:

 Named tuple with a, b, c: 0.124072679784 Named tuple, using index: 0.0447055962367 Class using __slots__, with a, b, c: 0.0409136944224 Dictionary with keys a, b, c: 0.0412045334915 Tuple with three values, using a constant key: 0.0449477955531 List with three values, using a constant key: 0.0331083467148 Tuple with three values, using a local key: 0.0453569025139 List with three values, using a local key: 0.033030056702 

PyPy结果:

 Named tuple with a, b, c: 0.00444889068604 Named tuple, using index: 0.00265598297119 Class using __slots__, with a, b, c: 0.00208616256714 Dictionary with keys a, b, c: 0.013897895813 Tuple with three values, using a constant key: 0.00275301933289 List with three values, using a constant key: 0.002760887146 Tuple with three values, using a local key: 0.002769947052 List with three values, using a local key: 0.00278806686401 

一些观点和想法:

1)您正在连续多次访问相同的索引。 您的实际程序可能使用随机或线性访问,这将有不同的行为。 特别是,会有更多的CPUcaching未命中。 使用您的实际程序,您可能会得到稍微不同的结果

2)OrderedDictionary是作为dict的封装来编写的,但是它会比dict慢。 这是一个非解决scheme。

3)你是否尝试过新式和旧式课程? (新的类inheritance自object ;旧式的类不)

4)你尝试过使用psyco还是Unladen Swallow ?

5)你的内部循环是修改数据还是只访问它? 在进入循环之前,将数据转化为最有效的可能forms是可能的,但在程序的其他地方使用最方便的forms。

(a)发明某种特定于工作负载的caching,并将我的数据的存储和检索卸载到类似memcachedb的进程中,以提高可伸缩性而不是单独执行或(b)重写为C扩展,与本机数据存储。 一个有序的字典types也许。

你可以从这开始: http : //www.xs4all.nl/~anthon/Python/ordereddict/

您可以通过添加__iter____getitem__方法来使您的类序列化,以使它们的顺序类似于(可索引和可​​迭代的)。

OrderedDict工作吗? 有几个可用的实现,它包含在Python31 collections模块中。