MSe*_*ert 6 python python-internals
我最近比较的性能collections.Counter来sorted进行比较检查(如果有的迭代包含具有相同数量相同的元素),并同时的大迭代表现Counter一般优于sorted它的短iterables慢得多.
使用line_profiler的瓶颈似乎是isinstance(iterable, collections.Mapping)在-check Counter.update:
%load_ext line_profiler # IPython
lst = list(range(1000))
%lprun -f Counter.update Counter(lst)
Run Code Online (Sandbox Code Playgroud)
给我:
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)
Run Code Online (Sandbox Code Playgroud)
因此,即使长度为1000次迭代,它也需要超过15%的时间.对于更短的迭代(例如,20个项目,它增加到60%).
我首先想到它与如何collections.Mapping使用有关__subclasshook__但在第一次检查后isinstance不再调用该方法.那么为什么检查isinstance(iterable, Mapping)这么慢?
性能实际上只与ABCMeta的__instancecheck__一系列支票相关联,这些支票被称为isinstance.
最重要的是,这里目睹的不良表现不是一些遗漏优化的结果,而是由于isinstance抽象基类是一个Python级别的操作,正如Jim所提到的那样.缓存正面和负面结果,但即使使用缓存结果,您也只需要查看每个循环几微秒,就可以遍历__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
Run Code Online (Sandbox Code Playgroud)
我们可以看到性能差异 - 是什么原因造成的?
对于一个字典
>>> %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})
Run Code Online (Sandbox Code Playgroud)
列表
>>> %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})
Run Code Online (Sandbox Code Playgroud)
我们可以看到,对于一个dict,Mapping抽象类' _abc_cache
>>> list(collections.abc.Mapping._abc_cache)
[dict]
Run Code Online (Sandbox Code Playgroud)
包括我们的字典,所以检查早期短路.对于列表显然不会命中正缓存,但映射_abc_negative_cache包含列表类型
>>> list(collections.abc.Mapping._abc_negative_cache)
[type,
list,
generator,
pandas.core.series.Series,
itertools.chain,
int,
map]
Run Code Online (Sandbox Code Playgroud)
以及现在的pd.Series类型,因为isinstance不止一次调用%timeit.在我们没有达到负缓存的情况下(就像系列的第一次迭代一样),Python会使用常规子类检查
cls.__subclasscheck__(subclass)
Run Code Online (Sandbox Code Playgroud)
其可以是远慢,诉诸子类钩和递归子类检查这里看到,那么高速缓存用于随后的加速比的结果.
| 归档时间: |
|
| 查看次数: |
3564 次 |
| 最近记录: |