【发布时间】:2017-07-11 17:58:30
【问题描述】:
我最近比较了 collections.Counter 和 sorted 的性能以进行比较检查(如果某些迭代包含相同数量的相同元素),而 Counter 的大迭代性能通常优于 sorted短迭代速度要慢得多。
使用line_profiler 的瓶颈似乎是isinstance(iterable, collections.Mapping)-签入Counter.update:
%load_ext line_profiler # IPython
lst = list(range(1000))
%lprun -f Counter.update Counter(lst)
给我:
Timer unit: 5.58547e-07 s
Total time: 0.000244643 s
File: ...\lib\collections\__init__.py
Function: update at line 581
Line # Hits Time Per Hit % Time Line Contents
==============================================================
581 def update(*args, **kwds):
601 1 8 8.0 1.8 if not args:
602 raise TypeError("descriptor 'update' of 'Counter' object "
603 "needs an argument")
604 1 12 12.0 2.7 self, *args = args
605 1 6 6.0 1.4 if len(args) > 1:
606 raise TypeError('expected at most 1 arguments, got %d' % len(args))
607 1 5 5.0 1.1 iterable = args[0] if args else None
608 1 4 4.0 0.9 if iterable is not None:
609 1 72 72.0 16.4 if isinstance(iterable, Mapping):
610 if self:
611 self_get = self.get
612 for elem, count in iterable.items():
613 self[elem] = count + self_get(elem, 0)
614 else:
615 super(Counter, self).update(iterable) # fast path when counter is empty
616 else:
617 1 326 326.0 74.4 _count_elements(self, iterable)
618 1 5 5.0 1.1 if kwds:
619 self.update(kwds)
因此,即使长度为 1000 次迭代,它也需要超过 15% 的时间。对于更短的迭代(例如 20 个项目,它增加到 60%)。
我首先认为这与collections.Mapping 如何使用__subclasshook__ 有关,但在第一个isinstance-check 之后不再调用该方法。那么为什么检查isinstance(iterable, Mapping) 这么慢呢?
【问题讨论】:
-
所以你的问题真的归结为为什么
isinstance对抽象基类的检查很慢?因为我不认为这是可迭代的和Mapping特定的。 -
@Mitch 可能,实际上似乎其他
collections.abc类在isinstance检查中同样慢。你知道是什么让 abc 的这些检查如此缓慢吗? :) -
我现在正在研究
__instancecheck__的实现,似乎没有发生什么太时髦的事情——当你错过缓存时性能会更差。打算再考虑一下。 -
isinstance带有ABCMeta类是 Python 级别的操作,不幸的是。在ABCMeta.__instancecheck__中进行了许多检查,传递除映射以外的任何内容都将采用最慢的可用路径。