Python中最快(访问)类似结构的对象是什么?
我正在优化一些代码,这些代码的主要瓶颈是运行和访问大量类似结构的对象。目前,为了可读性,我使用namedtuples。但一些使用“timeit”的快速基准测试表明,在性能是一个因素的情况下,这确实是一种错误的方法: 带a、b、c的命名元组:Python中最快(访问)类似结构的对象是什么?,python,performance,data-structures,Python,Performance,Data Structures,我正在优化一些代码,这些代码的主要瓶颈是运行和访问大量类似结构的对象。目前,为了可读性,我使用namedtuples。但一些使用“timeit”的快速基准测试表明,在性能是一个因素的情况下,这确实是一种错误的方法: 带a、b、c的命名元组: >>> timeit("z = a.c", "from __main__ import a") 0.38655471766332994 >>> timeit("z = b.c", "from __main__ impor
>>> timeit("z = a.c", "from __main__ import a")
0.38655471766332994
>>> timeit("z = b.c", "from __main__ import b")
0.14527461047146062
使用a、b、c:
>>> timeit("z = a.c", "from __main__ import a")
0.38655471766332994
>>> timeit("z = b.c", "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[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
使用本地键列出三个值:
>>> timeit("z = d[key]", "from __main__ import d, key")
0.11187358437882722
>>> timeit("z = e[key]", "from __main__ import e, key")
0.088604143037173344
首先,这些小小的timeit
测试是否会导致它们无效?我每次都运行了几次,以确保没有随机系统事件将它们抛出,并且结果几乎相同
词典似乎在性能和可读性之间提供了最佳平衡,类排在第二位。这是不幸的,因为,出于我的目的,我还需要对象像序列一样;因此,我选择了namedtuple
列表速度大大加快,但常量键无法维护;我必须创建一组索引常量,即KEY_1=1,KEY_2=2,等等,这也是不理想的
我是坚持这些选择,还是错过了其他选择?我可能会(a)发明某种特定于工作负载的缓存,并将数据的存储和检索转移到类似memcachedb的进程中,以提高可伸缩性,而不仅仅是性能,或者(b)将其重写为C扩展,使用本机数据存储。可能是一种有序的字典类型 您可以从以下内容开始:
您可以通过添加
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
和\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
你想要一份工作吗?Python31模块中包含了几种可用的实现方式。几点要点和想法:
您连续多次定时访问同一索引。您的实际程序可能使用随机或线性访问,这将有不同的行为。特别是,将有更多的CPU缓存未命中。使用实际程序可能会得到稍微不同的结果
OrderedDictionary是作为dict
的包装器编写的,因此它将比dict
慢。这不是解决办法
你试过新的和旧的课程吗?(新样式类继承自对象
;旧样式类不继承)
你试过使用或吗?(2020年更新-这两个项目已经停止)
您的内部循环是修改数据还是仅访问数据?在进入循环之前,可以将数据转换为最有效的形式,但在程序的其他地方使用最方便的形式
需要记住的一点是,命名的元组经过优化,可以作为元组访问。如果将访问器更改为a[2]
而不是a.c
,您将看到与元组类似的性能。原因是名称访问器正在有效地转换为对self[idx]的调用,因此需要支付索引和名称查找的费用
如果您的使用模式是按名称访问是常见的,而按元组访问则不是常见的,那么您可以编写一个与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]
但是当必须调用\uu getitem\uu
时,计时非常糟糕:
namedtuple.a : 0.473686933517
namedtuple[0] : 0.180409193039
struct.a : 0.180846214294
struct[0] : 1.32191514969
也就是说,与属性访问的\uuuu slots\uuu
类的性能相同(毫不奇怪,事实就是这样),但由于基于索引的访问中的双重查找,会带来巨大的损失。(值得注意的是,\uuuuu插槽\uuuuu
实际上对速度没有多大帮助。它可以节省内存,但没有它们的访问时间几乎相同。)
第三种选择是复制数据,例如从列表中创建子类,并将值存储在属性和列表数据中。然而,您实际上并没有获得与列表相同的性能。子类化带来了巨大的速度冲击(带来了对纯python重载的检查)。因此,在这种情况下,struct[0]仍然需要0.5s左右的时间(与原始列表的0.18相比),并且您的内存使用量增加了一倍,因此这可能不值得。这个问题已经相当老了(互联网时间),所以我想我今天会尝试复制您的测试,使用常规的CPython(2.7.6)和pypypy(2.2.1)看看各种方法的比较。(我还为命名元组添加了索引查找。)
这有点像一个微基准测试,所以YMMV,但是pypy似乎比CPython加快了命名元组访问30倍(而字典访问的速度只有3倍)
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
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
PyPy结果:
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
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
这个问题可能很快就会过时。显然,cpythondev对通过属性名访问命名元组值的性能做出了重大改进。这些更改计划于2019年10月底发布
请参阅:和。如果性能如此重要,为什么不使用C?@Skilldrick:这只是大型程序的一小部分,而大型程序得益于使用Python编写。将此部分重写为C扩展是一种选择,但有些不可取,因为其他代码也会涉及数据,这会使事情变得有点复杂。绩效很重要,但不是那么关键;如果不是因为可维护性的降低,我对列表提供的4倍改进非常满意。在我决定走哪条路之前,我只是在寻找其他的选择;我没有过早地优化。这是一个非常紧密的循环,其中