为什么contextmanager很慢

时间:2016-01-19 09:04:48

标签: python performance with-statement

一位同事向我指出,with声明可能很慢。所以我测量了,实际上从contextmanager函数中获取值比从Python 2.7中的生成器获取值要长20倍,在PyPy 2.6中甚至要长200倍。

为什么会这样?是否可以重写contextlib.contextmanager()以更快地运行?

供参考:

def value_from_generator():
    def inner(): yield 1

    value, = inner()
    return value

def value_from_with():
    @contextmanager
    def inner(): yield 1

    with inner() as value:
        return value

和时间:

$ python -m timeit 'value_from_generator()'
10000000 loops, best of 3: 0.169 usec per loop

$ python -m timeit 'value_from_with()'
100000 loops, best of 3: 3.04 usec per loop

2 个答案:

答案 0 :(得分:4)

使用profiler和contextlib的来源,我找到了:

value_from_with:
 ncalls  tottime  cumtime  filename:lineno(function)
1000000    1.415    4.802  value_from_with  # 1sec more than value_from_generator, likely caused by with statement
1000000    1.115    1.258  contextlib.py:37(__init__)  # better doc string of context manager instance
1000000    0.656    0.976  contextlib.py:63(__exit__)  # optional exception handling
1000000    0.575    1.833  contextlib.py:124(helper)  # "wrapped" in decorator
2000000    0.402    0.604  {built-in method next}  # why it's so expensive?
1000000    0.293    0.578  contextlib.py:57(__enter__)  # a next() call to the generator in try&except block (just for error msg)
2000000    0.203    0.203  inner1
1000000    0.143    0.143  {built-in method getattr}  # better doc string, called by __init__

value_from_generator:
 ncalls  tottime  cumtime  filename:lineno(function)
1000000    0.416    0.546  value_from_generator
2000000    0.130    0.130  inner2

它告诉我们:从发生器解包比使用next()更快; 功能调用很贵;异常处理是昂贵的...所以比较是不公平的,这个分析只是为了好玩。

它还告诉我们,每次"与"执行块时创建上下文管理器的实例(几乎不可避免)。除此之外,contextmanager做了一些工作以方便我们。如果你真的想要优化它,你可以编写一个上下文管理器类而不是使用装饰器

个人资料代码:

def inner1(): yield 1

def value_from_generator():
    value, = inner1()
    return value

# inner should not be created again and again
@contextmanager
def inner2(): yield 1

def value_from_with():
    with inner2() as value:
        return value

答案 1 :(得分:1)

这两个工具有点'不同的目的,所以比较他们的表现并不能真正展示任何东西。

Contextmanagers允许您在with块中执行代码之前和之后执行某些操作。常用的是在开始时占用资源,完成工作,执行清理,即数据库连接,文件访问等。

生成器允许您编写函数,在调用之间保存状态。常用于在不需要的(特定时刻)计算上节省资源,并在不同时存储所有操作结果的情况下节省内存。所以主要用于计算目的。