为什么 contextmanager 很慢
Why contextmanager is slow
一位同事向我指出,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
使用分析器和 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() 更快;
函数调用很昂贵;异常处理是昂贵的......所以比较是不公平的,这个分析只是为了好玩。
它还告诉我们,每次执行 "with" 块时,都会创建一个上下文管理器实例(几乎不可避免)。除此之外,contextmanager
还做了一些工作来方便我们。如果你真的想优化它,你可以写一个上下文管理器class而不是使用装饰器
配置代码:
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
这两个工具有 'slightly' 不同的用途,因此比较它们的性能并不能真正说明任何问题。
上下文管理器允许您在 with
块中执行代码之前和之后执行一些操作。常见的用途是启动时占用资源,做作业,执行清理,即DB连接,文件访问等
生成器允许您编写函数代码,保存调用之间的状态。通常用于在不需要的(特定时刻)计算上节省资源,并在不一次存储所有操作结果时节省内存。所以主要用于计算目的。
一位同事向我指出,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
使用分析器和 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() 更快; 函数调用很昂贵;异常处理是昂贵的......所以比较是不公平的,这个分析只是为了好玩。
它还告诉我们,每次执行 "with" 块时,都会创建一个上下文管理器实例(几乎不可避免)。除此之外,contextmanager
还做了一些工作来方便我们。如果你真的想优化它,你可以写一个上下文管理器class而不是使用装饰器
配置代码:
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
这两个工具有 'slightly' 不同的用途,因此比较它们的性能并不能真正说明任何问题。
上下文管理器允许您在 with
块中执行代码之前和之后执行一些操作。常见的用途是启动时占用资源,做作业,执行清理,即DB连接,文件访问等
生成器允许您编写函数代码,保存调用之间的状态。通常用于在不需要的(特定时刻)计算上节省资源,并在不一次存储所有操作结果时节省内存。所以主要用于计算目的。