Python性能:是否尝试-否?

发布于 2021-01-29 15:01:06

在我的一个类中,我有许多方法都从相同的字典中提取值。但是,如果其中一个方法尝试访问不存在的值,则它必须调用另一个方法以使该值与该键关联。

我目前已按以下方式实现此功能,其中findCrackDepth(tonnage)为self.lowCrackDepth [tonnage]分配一个值。

if tonnage not in self.lowCrackDepth:
    self.findCrackDepth(tonnage)
lcrack = self.lowCrackDepth[tonnage]

但是,我也有可能这样做

try:
    lcrack = self.lowCrackDepth[tonnage]
except KeyError:
    self.findCrackDepth(tonnage)
    lcrack = self.lowCrackDepth[tonnage]

我假设两者之间存在性能差异,这与值在字典中的频率有关。这个差异有多大?我正在生成数百万个这样的值(分布在该类的许多实例中的许多词典中),并且每次不存在该值时,它的值可能都会出现两次。

关注者
0
被浏览
100
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    这是一个微妙的问题,因为您需要注意避免“持久的副作用”,并且性能折衷取决于丢失键的百分比。因此,请考虑以下dil.py文件:

    def make(percentmissing):
      global d
      d = dict.fromkeys(range(100-percentmissing), 1)
    
    def addit(d, k):
      d[k] = k
    
    def with_in():
      dc = d.copy()
      for k in range(100):
        if k not in dc:
          addit(dc, k)
        lc = dc[k]
    
    def with_ex():
      dc = d.copy()
      for k in range(100):
        try: lc = dc[k]
        except KeyError:
          addit(dc, k)
          lc = dc[k]
    
    def with_ge():
      dc = d.copy()
      for k in range(100):
        lc = dc.get(k)
        if lc is None:
          addit(dc, k)
          lc = dc[k]
    

    以及一系列timeit呼叫,例如:

    $ python -mtimeit -s'import dil; dil.make(10)' 'dil.with_in()'
    10000 loops, best of 3: 28 usec per loop
    $ python -mtimeit -s'import dil; dil.make(10)' 'dil.with_ex()'
    10000 loops, best of 3: 41.7 usec per loop
    $ python -mtimeit -s'import dil; dil.make(10)' 'dil.with_ge()'
    10000 loops, best of 3: 46.6 usec per loop
    

    这表明,缺少10%的密钥,in检查实际上是最快的方法。

    $ python -mtimeit -s'import dil; dil.make(1)' 'dil.with_in()'
    10000 loops, best of 3: 24.6 usec per loop
    $ python -mtimeit -s'import dil; dil.make(1)' 'dil.with_ex()'
    10000 loops, best of 3: 23.4 usec per loop
    $ python -mtimeit -s'import dil; dil.make(1)' 'dil.with_ge()'
    10000 loops, best of 3: 42.7 usec per loop
    

    仅丢失1%的密钥,该exception方法 快(get无论哪种情况,该方法仍然是最慢的一种)。

    因此,为了获得最佳性能,除非 绝大多数 (99%以上)的查找成功,否则该in方法是可取的。

    当然,还有另一种优雅的可能性:添加像…这样的dict子类:

    class dd(dict):
       def __init__(self, *a, **k):
         dict.__init__(self, *a, **k)
       def __missing__(self, k):
         addit(self, k)
         return self[k]
    
    def with_dd():
      dc = dd(d)
      for k in range(100):
        lc = dc[k]
    

    然而…:

    $ python -mtimeit -s'import dil; dil.make(1)' 'dil.with_dd()'
    10000 loops, best of 3: 46.1 usec per loop
    $ python -mtimeit -s'import dil; dil.make(10)' 'dil.with_dd()'
    10000 loops, best of 3: 55 usec per loop
    

    …虽然确实很漂亮,但这并不是性能上的赢家-
    即便采用这种get方法,或更慢的速度,也要使用外观漂亮的代码来实现。(在defaultdict语义上类似于此类dd,如果适用的话,将是性能上的胜利,但这是因为__missing__在这种情况下,特殊方法是在经过优化的C代码中实现的)。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看