What is the fastest (to access) struct-like object in Python?
我正在优化一些代码,它们的主要瓶颈正在运行,并访问大量类似结构的对象。为了可读性,目前我使用的是namedtuples。但一些使用"TimeIt"的快速基准测试表明,如果性能是一个因素,那么这确实是一种错误的方式:
名为a、b、c的元组:
1 2 | >>> timeit("z = a.c","from __main__ import a") 0.38655471766332994 |
使用
1 2 | >>> timeit("z = b.c","from __main__ import b") 0.14527461047146062 |
带A、B、C键的字典:
1 2 | >>> timeit("z = c['c']","from __main__ import c") 0.11588272541098377 |
使用常量键创建三个值的元组:
1 2 | >>> timeit("z = d[2]","from __main__ import d") 0.11106188992948773 |
使用常量键列出三个值:
1 2 | >>> timeit("z = e[2]","from __main__ import e") 0.086038238242508669 |
三个值的元组,使用本地键:
1 2 | >>> timeit("z = d[key]","from __main__ import d, key") 0.11187358437882722 |
使用本地键列出三个值:
1 2 | >>> timeit("z = e[key]","from __main__ import e, key") 0.088604143037173344 |
首先,这些小的
字典似乎在性能和可读性之间提供了最佳的平衡,类排在第二位。这是不幸的,因为出于我的目的,我也需要对象的顺序,因此我选择了命名的两个。
列表速度快得多,但常量键是不可维护的;我必须创建一组索引常量,即key_1=1、key_2=2等,这也是不理想的。
我是坚持这些选择,还是有我错过的选择?
要记住的一件事是,命名的双元组被优化为作为元组访问。如果将访问器更改为
如果您的使用模式是这样的:按名称访问是常见的,但按元组访问不是常见的,那么您可以编写一个与执行相反操作的NamedDuple等效的快速方法:将索引查找延迟为按名称访问。但是,您将支付索引查找的价格。下面是一个快速实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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] |
但当必须调用
1 2 3 4 | namedtuple.a : 0.473686933517 namedtuple[0] : 0.180409193039 struct.a : 0.180846214294 struct[0] : 1.32191514969 |
也就是说,与属性访问的
第三种选择是复制数据,例如列表中的子类,并将值存储在属性和列表数据中。然而,您实际上并没有获得与列表中相同的性能。子类化(引入纯Python重载的检查)有很大的速度冲击。因此,在这种情况下,struct[0]仍然需要大约0.5秒(与原始列表的0.18相比),并且您的内存使用量增加了一倍,因此这可能不值得。
这个问题相当古老(互联网时间),所以我想今天我会尝试复制你的测试,包括常规的cpython(2.7.6)和pypy(2.2.1),看看各种方法之间的比较。(我还为命名的元组添加了索引查找。)
这是一个微基准测试,所以ymmv,但pypy似乎比cpython加快了30倍的命名tuple访问速度(而字典访问速度只加快了3倍)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | from collections import namedtuple STest = namedtuple("TEST","a b c") 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 = a.c","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 = b.c","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结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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)您在一行中多次访问同一索引。您的实际程序可能使用随机或线性访问,这将具有不同的行为。特别是,会有更多的CPU缓存未命中。使用实际程序可能会得到稍微不同的结果。
2)ORDEREDDICTIONARY写在
3)你试过新旧风格的课程吗?(新样式类继承自
4)您是否尝试过使用psyco或unladen燕子?
5)您的内部循环是修改数据还是只访问数据?在进入循环之前,可以将数据转换成最有效的形式,但在程序的其他地方使用最方便的形式。
我可能会尝试(a)发明某种特定于工作负载的缓存,并将数据的存储和检索卸载到类似memcachedb的进程,以提高可扩展性,而不是只提高性能,或者(b)使用本机数据存储将数据重写为C扩展。可能是一种有序的字典类型。
你可以从这个开始:http://www.xs4all.nl/~anthon/python/ordereddict/
您可以通过添加
一个