当前位置: 首页 > 面试题库 >

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

田曜瑞
2023-03-14
问题内容

我最近比较了collections.Counterto的性能以sorted进行比较检查(如果某些可迭代对象包含相同元素且具有相同数量),而的大可迭代性能Counter通常要好于sorted短可迭代对象的性能。

使用line_profiler瓶颈似乎是isinstance(iterable, collections.Mapping)-check in
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不再调用该方法。那么为什么检查isinstance(iterable, Mapping)这么慢?


问题答案:

性能实际上只是与ABCMeta的__instancecheck__支票集合联系在一起,这称为isinstance

最重要的是,这里看到的性能不佳不是由于缺少一些优化,而是isinstanceJim提到的抽象基类是Python级别的操作的结果。正和负的结果都会被缓存,但是即使有缓存的结果,您也要在每个循环中花几微秒的时间来遍历__instancecheck__ABCMeta类方法中的条件。

一个例子

考虑一些不同的空结构。

>>> 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

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

为了一个命令

>>> %lprun -f abc.ABCMeta.__instancecheck__ isinstance(dict(), collections.abc.Mapping)
Timer unit: 6.84247e-07 s
Total time: 1.71062e-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     28.0          subclass = instance.__class__
   182         1           16     16.0     64.0          if subclass in cls._abc_cache:
   183         1            2      2.0      8.0              return True
   184                                                   subtype = type(instance)
   185                                                   if subtype is subclass:
   186                                                       if (cls._abc_negative_cache_version ==
   187                                                           ABCMeta._abc_invalidation_counter and
   188                                                           subclass in cls._abc_negative_cache):
   189                                                           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})

清单

>>> %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]

包括我们的字典,因此检查会尽早短路。对于列表,显然不会命中正缓存,但是“映射”_abc_negative_cache包含列表类型

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

以及现在的pd.Series类型,这是因为使用isinstance多次调用了%timeit。在我们没有命中负缓存的情况下(例如Series的第一次迭代),Python会使用带有

cls.__subclasscheck__(subclass)

其可以是
慢,诉诸子类钩和递归子类检查这里看到,那么高速缓存用于随后的加速比的结果。



 类似资料:
  • 问题内容: 我对java8流中和方法之间的区别感到困惑。例如, 我得到空的输出在这里,但如果我改变地图就像 我可以得到 {1 = 1,2 = 2} 为什么只是没有运行方法?它们之间有什么区别? 问题答案: 什么都不做,因为在执行终端操作之前不会处理流管道。因此,残留物为空。 在流管道中添加终端操作将导致针对终端操作所需的每个元素执行操作(某些终端操作仅需要一个元素,而其他终端操作则需要的所有元素)

  • 问题内容: 我尝试创建映射,期望它会自动增长。如手册页中所指定: MAP_GROWSDOWN 该标志用于堆栈。它向内核虚拟内存系统指示该映射应在内存中向下扩展。返回地址比在进程的虚拟地址空间中实际创建的内存区域低一页。 触摸映射下方的“防护”页面中的地址,将导致 映射增加一页 。可以重复这种增长,直到该映射增长到下一个较低映射的高端的页面内为止,此时触摸“防护”页面将产生 信号。 因此,我编写了以

  • 问题内容: 问题答案: 在3.0之前的Python版本中,有两种字符串:“普通字符串”和“ unicode字符串”。普通字符串()不能表示拉丁字母之外的字符(为简单起见,忽略代码页的详细信息)。Unicode字符串()可以代表任何字母的字符,包括虚构的字母,例如Klingon。 那么,为什么要使用两种字符串,仅使用Unicode会更好,因为这将涵盖所有情况?最好只使用Unicode,但是在Unic

  • 问题内容: 这是所有编程语言所共有的吗?在进行多次打印后再执行println似乎更快,但是将所有内容移动到字符串中并仅进行打印似乎最快。为什么? 编辑:例如,Java可以在不到一秒钟的时间内找到所有高达100万的质数- 但要进行打印,然后在自己的println中将它们全部输出可能需要几分钟!最多可打印100亿小时! 例如: 问题答案: 速度并不慢,而是由主机操作系统提供的与控制台连接的基础。 您可

  • 问题内容: 我对此感到困惑 现在让我们来看看numpy: 神圣的CPU周期蝙蝠侠! 使用改进,但恕我直言仍然不够 numpy.version.version =‘1.5.1’ 如果您想知道在第一个示例中是否跳过了列表创建以进行优化,则不是: 问题答案: Numpy已针对大量数据进行了优化。给它一个很小的3长度数组,毫不奇怪,它的性能很差。 考虑单独的测试 输出是 似乎是数组的归零一直花费在nump

  • 问题内容: Magento通常这么慢吗? 这是我的第一次使用体验,管理面板只需花一些时间即可加载和保存更改。这是带有测试数据的默认安装。 托管该服务器的服务器可超快地服务于其他非Magento站点。Magento使它如此缓慢的PHP代码有什么用,该如何解决? 问题答案: 我只是切身参与优化Magento的性能,但这是系统速度如此缓慢的一些原因 Magento的某些部分使用在MySQL之上实现的EA

  • 问题内容: 在有人质疑使用的事实之前,我先说一下,出于内存和性能的原因,我需要在特定的应用程序中使用它。[1] 因此,到目前为止,我一直使用并假定这是最有效的方法。但是,自古以来我就注意到它是软件的瓶颈。[2] 然后,就在最近,我试图用一个巨大的映射替换,在该映射中放置/获取字符串,以便每次获得唯一的实例。我以为这会慢一些…但是事实恰恰相反!它快得多了!通过推送/轮询地图(实现完全相同)来替换,可

  • 问题内容: 我发现这段代码使用了几次(也使用了类似的代码代替)。 为什么在这里检查? 寻找对 男人 ,我发现关于下面的文字,但即使我参观不赐教。 EINTR中断函数调用(POSIX.1);参见signal(7)。 问题答案: 如果在进行系统调用时发生信号,许多系统调用将报告错误代码。实际上没有发生错误,只是因为系统无法自动恢复系统调用,所以才报告这种错误。这种编码模式仅在发生这种情况时重试系统调用