为什么 any() 和 all() 在处理布尔值时效率低下?
Why are any() and all() inefficient at treating booleans?
我刚刚在玩 timeit
和 and, or, any(), all()
时发现了一些我认为可以在这里分享的东西。这是衡量性能的脚本:
def recursion(n):
"""A slow way to return a True or a False boolean."""
return True if n == 0 else recursion(n-1)
def my_function():
"""The function where you perform all(), any(), or, and."""
a = False and recursion(10)
if __name__ == "__main__":
import timeit
setup = "from __main__ import my_function"
print(timeit.timeit("my_function()", setup=setup))
下面是一些时间:
a = False and recursion(10)
0.08799480279344607
a = True or recursion(10)
0.08964192798430304
正如预期的那样,True or recursion(10)
和 False and recursion(10)
的计算速度非常快,因为只有第一项很重要,并且立即执行 returns 运算。
a = recursion(10) or True # recursion() is False
1.4154556830951606
a = recursion(10) and False # recursion() is True
1.364157978046478
行中有 or True
或 and False
不会加快此处的计算速度,因为它们是第二个评估,必须首先执行整个递归。虽然烦人,但它符合逻辑,并且遵循操作优先级规则。
更令人惊讶的是,all()
和any()
无论大小写总是最差的:
a = all(i for i in (recursion(10), False))) # recursion() is False
1.8326778537880273
a = all(i for i in (False, recursion(10))) # recursion() is False
1.814645767348111
我原以为第二次评估会比第一次评估快得多。
a = any(i for i in (recursion(10), True))) # recursion() is True
1.7959248761901563
a = any(i for i in (True, recursion(10))) # recursion() is True
1.7930442127481
同样未满足的期望。
所以看起来 any()
和 all()
远不是分别写大 or
和大 and
的方便方法如果性能在您的应用程序中很重要。这是为什么?
编辑:根据评论,元组生成似乎很慢。我看不出为什么 Python 本身不能使用这个:
def all_faster(*args):
Result = True
for arg in args:
if not Result:
return False
Result = Result and arg
return True
def any_faster(*args):
Result = False
for arg in args:
if Result:
return True
Result = Result or arg
return False
它已经比内置函数更快,而且似乎有短路机制。
a = faster_any(False, False, False, False, True)
0.39678611016915966
a = faster_any(True, False, False, False, False)
0.29465180389252055
a = faster_any(recursion(10), False) # recursion() is True
1.5922580174283212
a = faster_any(False, recursion(10)) # recursion() is True
1.5799157924820975
a = faster_all(False, recursion(10)) # recursion() is True
1.6116566893888375
a = faster_all(recursion(10), False) # recursion() is True
1.6004807187900951
Edit2:好吧,参数逐个传递速度更快,但生成器速度更慢。
any
和all
短接就好了
问题是,在这两种情况下,您都必须先构建 tuple
,然后再将其传递给 any
,因此顺序没有区别:所花费的时间仍然是相同的。让我们用一个变量来分解它:
t = (True, recursion(10)) # recursion is called
a = any(i for i in t) # this is very fast, only boolean testing
当你到达第二行时,时间已经用完了。
与短路的and
或or
不同
any
或 all
有趣的情况是当您在测试时评估数据:
any(recusion(x) for x in (10,20,30))
如果您想避免求值,可以将 lambda 元组(内联函数)传递给 any
并调用函数:
现在:
a = any(i() for i in (lambda:recursion(10), lambda:True)))
和:
a = any(i() for i in (lambda:True,lambda:recursion(10))))
有一个非常不同的执行时间(后者是瞬时的)
实际上,any()
相当于or
的链,all()
相当于and
的链,包括短路。问题在于您执行基准测试的方式。
考虑以下几点:
def slow_boolean_gen(n, value=False):
for x in range(n - 1):
yield value
yield not value
generator = slow_boolean_gen(10)
print([x for x in generator])
# [False, False, False, False, False, False, False, False, False, True]
以及以下微基准:
%timeit generator = slow_boolean_gen(10, True); next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator)
# 492 ns ± 35.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator)
# 1.18 µs ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator)
# 1.19 µs ± 11.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator)
# 473 ns ± 6.27 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); any(x for x in generator)
# 745 ns ± 15 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); any(x for x in generator)
# 1.29 µs ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); all(x for x in generator)
# 1.3 µs ± 22.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); all(x for x in generator)
# 721 ns ± 8.05 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); any([x for x in generator])
# 1.03 µs ± 28.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); any([x for x in generator])
# 1.09 µs ± 27.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); all([x for x in generator])
# 1.05 µs ± 11.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); all([x for x in generator])
# 1.02 µs ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
您可以清楚地看到短路正在起作用,但是如果您首先构建 list
,这需要一个恒定的时间来抵消您从短路中获得的任何性能增益。
编辑:
手动实施不会给我们带来任何性能提升:
def all_(values):
result = True
for value in values:
result = result and value
if not result:
break
return result
def any_(values):
result = False
for value in values:
result = result or value
if result:
break
return result
%timeit generator = slow_boolean_gen(10, True); any_(x for x in generator)
# 765 ns ± 6.76 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); any_(x for x in generator)
# 1.48 µs ± 8.97 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); all_(x for x in generator)
# 1.47 µs ± 5.71 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); all_(x for x in generator)
# 765 ns ± 8.76 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
我刚刚在玩 timeit
和 and, or, any(), all()
时发现了一些我认为可以在这里分享的东西。这是衡量性能的脚本:
def recursion(n):
"""A slow way to return a True or a False boolean."""
return True if n == 0 else recursion(n-1)
def my_function():
"""The function where you perform all(), any(), or, and."""
a = False and recursion(10)
if __name__ == "__main__":
import timeit
setup = "from __main__ import my_function"
print(timeit.timeit("my_function()", setup=setup))
下面是一些时间:
a = False and recursion(10)
0.08799480279344607
a = True or recursion(10)
0.08964192798430304
正如预期的那样,True or recursion(10)
和 False and recursion(10)
的计算速度非常快,因为只有第一项很重要,并且立即执行 returns 运算。
a = recursion(10) or True # recursion() is False
1.4154556830951606
a = recursion(10) and False # recursion() is True
1.364157978046478
行中有 or True
或 and False
不会加快此处的计算速度,因为它们是第二个评估,必须首先执行整个递归。虽然烦人,但它符合逻辑,并且遵循操作优先级规则。
更令人惊讶的是,all()
和any()
无论大小写总是最差的:
a = all(i for i in (recursion(10), False))) # recursion() is False
1.8326778537880273
a = all(i for i in (False, recursion(10))) # recursion() is False
1.814645767348111
我原以为第二次评估会比第一次评估快得多。
a = any(i for i in (recursion(10), True))) # recursion() is True
1.7959248761901563
a = any(i for i in (True, recursion(10))) # recursion() is True
1.7930442127481
同样未满足的期望。
所以看起来 any()
和 all()
远不是分别写大 or
和大 and
的方便方法如果性能在您的应用程序中很重要。这是为什么?
编辑:根据评论,元组生成似乎很慢。我看不出为什么 Python 本身不能使用这个:
def all_faster(*args):
Result = True
for arg in args:
if not Result:
return False
Result = Result and arg
return True
def any_faster(*args):
Result = False
for arg in args:
if Result:
return True
Result = Result or arg
return False
它已经比内置函数更快,而且似乎有短路机制。
a = faster_any(False, False, False, False, True)
0.39678611016915966
a = faster_any(True, False, False, False, False)
0.29465180389252055
a = faster_any(recursion(10), False) # recursion() is True
1.5922580174283212
a = faster_any(False, recursion(10)) # recursion() is True
1.5799157924820975
a = faster_all(False, recursion(10)) # recursion() is True
1.6116566893888375
a = faster_all(recursion(10), False) # recursion() is True
1.6004807187900951
Edit2:好吧,参数逐个传递速度更快,但生成器速度更慢。
any
和all
短接就好了
问题是,在这两种情况下,您都必须先构建 tuple
,然后再将其传递给 any
,因此顺序没有区别:所花费的时间仍然是相同的。让我们用一个变量来分解它:
t = (True, recursion(10)) # recursion is called
a = any(i for i in t) # this is very fast, only boolean testing
当你到达第二行时,时间已经用完了。
与短路的and
或or
不同
any
或 all
有趣的情况是当您在测试时评估数据:
any(recusion(x) for x in (10,20,30))
如果您想避免求值,可以将 lambda 元组(内联函数)传递给 any
并调用函数:
现在:
a = any(i() for i in (lambda:recursion(10), lambda:True)))
和:
a = any(i() for i in (lambda:True,lambda:recursion(10))))
有一个非常不同的执行时间(后者是瞬时的)
实际上,any()
相当于or
的链,all()
相当于and
的链,包括短路。问题在于您执行基准测试的方式。
考虑以下几点:
def slow_boolean_gen(n, value=False):
for x in range(n - 1):
yield value
yield not value
generator = slow_boolean_gen(10)
print([x for x in generator])
# [False, False, False, False, False, False, False, False, False, True]
以及以下微基准:
%timeit generator = slow_boolean_gen(10, True); next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator)
# 492 ns ± 35.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator) or next(generator)
# 1.18 µs ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator)
# 1.19 µs ± 11.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator) and next(generator)
# 473 ns ± 6.27 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); any(x for x in generator)
# 745 ns ± 15 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); any(x for x in generator)
# 1.29 µs ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); all(x for x in generator)
# 1.3 µs ± 22.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); all(x for x in generator)
# 721 ns ± 8.05 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); any([x for x in generator])
# 1.03 µs ± 28.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); any([x for x in generator])
# 1.09 µs ± 27.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); all([x for x in generator])
# 1.05 µs ± 11.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); all([x for x in generator])
# 1.02 µs ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
您可以清楚地看到短路正在起作用,但是如果您首先构建 list
,这需要一个恒定的时间来抵消您从短路中获得的任何性能增益。
编辑:
手动实施不会给我们带来任何性能提升:
def all_(values):
result = True
for value in values:
result = result and value
if not result:
break
return result
def any_(values):
result = False
for value in values:
result = result or value
if result:
break
return result
%timeit generator = slow_boolean_gen(10, True); any_(x for x in generator)
# 765 ns ± 6.76 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); any_(x for x in generator)
# 1.48 µs ± 8.97 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, True); all_(x for x in generator)
# 1.47 µs ± 5.71 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit generator = slow_boolean_gen(10, False); all_(x for x in generator)
# 765 ns ± 8.76 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)