为什么在Python中contextlib和With语句的开销会达到惊人的[50X],以及该怎么做

为什么在Python中contextlib和With语句的开销会达到惊人的[50X],以及该怎么做,python,with-statement,Python,With Statement,在查找性能缺陷的过程中,我最终确定问题的根源是contextlib包装器。开销相当惊人,我没想到这会成为经济放缓的根源。经济放缓在50倍的范围内,我无法承受这种循环。如果它有可能让事情变得如此缓慢,我肯定会很感激文档中的警告 这似乎是2010年以来就知道的 它有一套你可以尝试的基准。在运行之前,请将simple\u catch()中的fn更改为fn()。感谢DSM指出这一点 我感到惊讶的是,自那时以来,情况没有改善。我能怎么办?我可以下拉列表尝试/例外,但我希望有其他方法来处理它。以下是一些新的

在查找性能缺陷的过程中,我最终确定问题的根源是contextlib包装器。开销相当惊人,我没想到这会成为经济放缓的根源。经济放缓在50倍的范围内,我无法承受这种循环。如果它有可能让事情变得如此缓慢,我肯定会很感激文档中的警告

这似乎是2010年以来就知道的

它有一套你可以尝试的基准。在运行之前,请将
simple\u 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\u 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似乎无法删除的是对象创建,在本例中,对象创建被轻松删除


不幸的是,尤其是对于
contextlib
,即使是PyPy也很难对其进行优化。这通常是正常的,因为尽管创建对象+函数调用+创建生成器的成本很高,但与通常的做法相比,成本很低

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



还有,派比。Dat JIT要快。

我不知道你的说法是否正确,但Python社区通常对性能不感兴趣。我运行了它,它似乎验证了你的观点。但很多时候,问题在于基准测试,而不是实际的系统,我在假设的“C++在这方面和那方面太慢”中看到了这一点。在这篇文章中,我不太精通Python。基准测试中有一个主要缺陷——如果你看一下
simple\u catch
函数,它实际上并没有调用
fn
,它只是简单地命名了它。这就是为什么两个
simple\u catch
结果实际上是相同的。(这只是一个评论,不是一个答案,因为contextlib方法确实较慢,尽管当被调用的函数实际执行某些操作时,这几乎没有什么大不了的。)@hpaulj鉴于contextlib(以及with)的实用性,任何速度提升都是值得赞赏的。如果这意味着将其编码为C模块,那就这样吧。@san我在pypy上运行了这个程序,有趣的是,它显示了同样的问题。很高兴看到pypy优化了开销。缓存确实是个好主意,谢谢!我在一台速度慢得多的机器上,而且也是32位的,这可能解释了感知到的开销量的差异。