Python性能:尝试 - 不在或不在?

Wil*_*uck 3 python performance

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

我目前实现如下,其中findCrackDepth(tonnage)为self.lowCrackDepth [tonnage]赋值.

if tonnage not in self.lowCrackDepth:
    self.findCrackDepth(tonnage)
lcrack = self.lowCrackDepth[tonnage]
Run Code Online (Sandbox Code Playgroud)

但是,我也可以这样做

try:
    lcrack = self.lowCrackDepth[tonnage]
except KeyError:
    self.findCrackDepth(tonnage)
    lcrack = self.lowCrackDepth[tonnage]
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*lli 14

这是一个微妙的问题,因为你需要注意避免"持久的副作用",性能权衡取决于缺失键的百分比.因此,请考虑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]
Run Code Online (Sandbox Code Playgroud)

以及一系列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
Run Code Online (Sandbox Code Playgroud)

这表明,如果缺少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
Run Code Online (Sandbox Code Playgroud)

只有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]
Run Code Online (Sandbox Code Playgroud)

然而...:

$ 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
Run Code Online (Sandbox Code Playgroud)

...虽然光滑,但这不是性能优胜者 - 即使采用这种get方法,也可能更慢,只需使用更好看的代码即可.(在defaultdict语义上类似于这个dd类,如果适用的话,将是性能获胜,但这是因为__missing__在这种情况下,特殊方法是在经过充分优化的C代码中实现的).