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

标签 python with-statement

在寻找性能错误的过程中,我终于确定问题的根源是 contextlib 包装器。开销相当惊人,我没想到这是减速的根源。减速在 50 倍的范围内,我无法承受循环。如果它有可能显着减慢速度,我肯定会感谢文档中的警告。

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

它有一组您可以尝试的基准。运行前请将simple_catch()中的fn改为fn()。谢谢 DSM 指出这一点。

令我惊讶的是,自那时以来情况没有改善。我该怎么办?我可以下拉尝试/排除,但我希望有其他方法可以处理它。

最佳答案

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

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)

我让 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

对于 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

开销比您声称的要小得多。此外,相对于手动变体的 try...catch 而言,PyPy 似乎无法消除的唯一开销是对象创建,它在这种情况。


不幸的是with is way too involved for good optimization by CPython ,尤其是关于 contextlib ,即使 PyPy 也很难优化。这通常没问题,因为虽然对象创建 + 函数调用 + 创建生成器的成本很高,但与通常的做法相比,它便宜

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


还有,PyPy。 Dat JIT

关于python - 为什么 contextlib 和 Python 中的 With 语句有惊人的开销 [50X] 以及如何处理它,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26152934/

相关文章:

Pythons 'with' -语句 : correctly nest/derive classes with __enter__/__exit__

javascript - "with"在 JavaScript 中做什么?

python - With语句和线程:Making function execute before run

python - 如何使用python将表单数据插入MYSQL

python - 由于 python-magic 无法找到 libmagic,Python 上的 Pushbullet 会引发导入错误?

python - 从文本文件加载keras模型

python - 做多线程txt文件I/O时遇到 "MemoryError",寻找更好的解决方案

python - 找出返回值将被解包成多少个值

python - pyYAML,期望 NodeEvent,但得到 DocumentEndEvent

python - "with"语句适用于 Windows 但不适用于 Ubuntu