为什么检查isinstance(某物,映射)这么慢?

2024-04-26 12:15:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我最近比较了collections.Counter和{}的性能,以便进行比较检查(如果某些iterable包含相同数量的相同元素),虽然{}的大iterable性能通常比{}好,但是对于短iterable来说,它要慢得多。在

使用line_profiler的瓶颈似乎是isinstance(iterable, collections.Mapping)-签入{}:

%load_ext line_profiler  # IPython
lst = list(range(1000))
%lprun -f Counter.update Counter(lst)

给我:

^{pr2}$

因此,即使长度为1000 Iterable,也需要15%以上的时间。对于更短的iterables(例如20个项目,它增加到60%)。在

我最初认为它与collections.Mapping如何使用__subclasshook__有关,但是在第一次isinstance检查之后,这个方法就不再被调用了。那么为什么检查isinstance(iterable, Mapping)这么慢?在


Tags: 元素数量ipythonlinecounterload性能iterable
1条回答
网友
1楼 · 发布于 2024-04-26 12:15:34

性能实际上只与ABCMeta's ^{}中的一个检查集合有关,该集合由^{}调用。在

底线是,这里所看到的低性能并不是由于某些缺少优化的结果,而是由于isinstance的结果,抽象基类是Python级别的操作,如Jim所述。正结果和负结果都会被缓存,但是即使使用缓存的结果,您也需要在每个循环中查看几微秒,以便遍历ABCMeta类的__instancecheck__方法中的条件。在


示例

考虑一些不同的空结构。在

>>> d = dict; l = list(); s = pd.Series()

>>> %timeit isinstance(d, collections.abc.Mapping)
100000 loops, best of 3: 1.99 µs per loop

>>> %timeit isinstance(l, collections.abc.Mapping)
100000 loops, best of 3: 3.16 µs per loop # caching happening

>>> %timeit isinstance(s, collections.abc.Mapping)
100000 loops, best of 3: 3.26 µs per loop # caching happening

我们可以看到性能差异-是什么原因造成的?在

对于dict

^{pr2}$

列表

>>> %lprun -f abc.ABCMeta.__instancecheck__ isinstance(list(), collections.abc.Mapping)
Timer unit: 6.84247e-07 s
Total time: 3.07911e-05 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   178                                               def __instancecheck__(cls, instance):
   179                                                   """Override for isinstance(instance, cls)."""
   180                                                   # Inline the cache checking
   181         1            7      7.0     15.6          subclass = instance.__class__
   182         1           17     17.0     37.8          if subclass in cls._abc_cache:
   183                                                       return True
   184         1            2      2.0      4.4          subtype = type(instance)
   185         1            2      2.0      4.4          if subtype is subclass:
   186         1            3      3.0      6.7              if (cls._abc_negative_cache_version ==
   187         1            2      2.0      4.4                  ABCMeta._abc_invalidation_counter and
   188         1           10     10.0     22.2                  subclass in cls._abc_negative_cache):
   189         1            2      2.0      4.4                  return False
   190                                                       # Fall back to the subclass check.
   191                                                       return cls.__subclasscheck__(subclass)
   192                                                   return any(cls.__subclasscheck__(c) for c in {subclass, subtype})

我们可以看到,对于dict,映射抽象类_abc_cache

>>> list(collections.abc.Mapping._abc_cache)
[dict]

包括我们的dict,所以要提前检查短路。显然,对于一个列表,肯定的缓存不会被命中,但是映射的_abc_negative_cache包含列表类型

>>> list(collections.abc.Mapping._abc_negative_cache)
[type,
 list,
 generator,
 pandas.core.series.Series,
 itertools.chain,
 int,
 map]

以及现在的pd系列类型,这是使用%timeit多次调用isinstance的结果。在我们没有命中负缓存的情况下(比如一个系列的第一次迭代),Python使用常规的子类check with

cls.__subclasscheck__(subclass)

这可能会慢得多,通过子类钩子和递归子类检查seen here,然后缓存结果以备后续加速。在

相关问题 更多 >