为什么contextlib的惊人开销[50X]和Python中的With语句以及如何处理它

san*_*san 5 python with-statement

在寻找性能错误的过程中,我终于发现问题的根源是contextlib包装器.开销非常惊人,我没想到会成为经济放缓的源头.减速在50倍的范围内,我不能在循环中拥有它.我肯定会感谢文档中的警告,如果它有可能显着降低速度.

自2010年以来,这似乎已为人所知https://gist.github.com/bdarnell/736778

它有一套你可以尝试的基准测试.请更改fnfn()simple_catch()运行前.谢谢,DSM指出了这一点.

我很惊讶自那时以来情况没有改善.我能做些什么呢?我可以下载尝试/除外,但我希望还有其他方法来处理它.

Vee*_*rac 3

以下是一些新的时间安排:

import contextlib
import timeit

def work_pass():
    pass

def work_fail():
    1/0

def simple_catch(fn):
    try:
        fn()
    except Exception:
        pass

@contextlib.contextmanager
def catch_context():
    try:
        yield
    except Exception:
        pass

def with_catch(fn):
    with catch_context():
        fn()

class ManualCatchContext(object):
    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        return True

def manual_with_catch(fn):
    with ManualCatchContext():
        fn()

preinstantiated_manual_catch_context = ManualCatchContext() 
def manual_with_catch_cache(fn):
    with preinstantiated_manual_catch_context:
        fn()

setup = 'from __main__ import simple_catch, work_pass, work_fail, with_catch, manual_with_catch, manual_with_catch_cache'
commands = [
    'simple_catch(work_pass)',
    'simple_catch(work_fail)',
    'with_catch(work_pass)',
    'with_catch(work_fail)',
    'manual_with_catch(work_pass)',
    'manual_with_catch(work_fail)',
    'manual_with_catch_cache(work_pass)',
    'manual_with_catch_cache(work_fail)',
    ]
for c in commands:
    print c, ': ', timeit.timeit(c, setup)
Run Code Online (Sandbox Code Playgroud)

simple_catch 实际上已经调用了该函数,并且添加了两个新的基准。

这是我得到的:

>>> python2 bench.py
simple_catch(work_pass) :  0.413918972015
simple_catch(work_fail) :  3.16218209267
with_catch(work_pass) :  6.88726496696
with_catch(work_fail) :  11.8109841347
manual_with_catch(work_pass) :  1.60508012772
manual_with_catch(work_fail) :  4.03651213646
manual_with_catch_cache(work_pass) :  1.32663416862
manual_with_catch_cache(work_fail) :  3.82525682449
python2 p.py.py  33.06s user 0.00s system 99% cpu 33.099 total
Run Code Online (Sandbox Code Playgroud)

对于 PyPy 来说:

>>> pypy bench.py
simple_catch(work_pass) :  0.0104489326477
simple_catch(work_fail) :  0.0212869644165
with_catch(work_pass) :  0.362847089767
with_catch(work_fail) :  0.400238037109
manual_with_catch(work_pass) :  0.0223228931427
manual_with_catch(work_fail) :  0.0208241939545
manual_with_catch_cache(work_pass) :  0.0138869285583
manual_with_catch_cache(work_fail) :  0.0213649272919
Run Code Online (Sandbox Code Playgroud)

开销比你声称的要小得多。try此外,相对于手动变体而言,PyPy 似乎无法删除的唯一catch开销是对象创建,在这种情况下可以轻松删除。


不幸的with是,CPython 的优化涉及太多尤其contextlib是 PyPy 也很难优化。这通常是可以的,因为虽然对象创建+函数调用+创建生成器的成本很高,但与通常所做的相比还是便宜的。

如果您确定with是造成大部分开销的原因,请将上下文管理器转换为缓存实例,就像我一样。如果这仍然太大,那么您的系统设计方式可能存在更大的问题。考虑扩大 s 的范围with(通常不是一个好主意,但如果需要的话可以接受)。


另外,PyPy。JIT 一定要