Python性能:是否尝试-否?
在我的一个类中,我有许多方法都从相同的字典中提取值。但是,如果其中一个方法尝试访问不存在的值,则它必须调用另一个方法以使该值与该键关联。
我目前已按以下方式实现此功能,其中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]
我假设两者之间存在性能差异,这与值在字典中的频率有关。这个差异有多大?我正在生成数百万个这样的值(分布在该类的许多实例中的许多词典中),并且每次不存在该值时,它的值可能都会出现两次。
-
这是一个微妙的问题,因为您需要注意避免“持久的副作用”,并且性能折衷取决于丢失键的百分比。因此,请考虑以下
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代码中实现的)。