【问题标题】:why is "any()" running slower than using loops?为什么“any()”的运行速度比使用循环慢?
【发布时间】:2015-11-10 15:54:56
【问题描述】:

我一直在从事一个项目,该项目管理大量单词并通过大量测试来验证列表中的每个单词。有趣的是,每次我使用像 itertools 模块这样“更快”的工具时,它们似乎都变慢了。

最后我决定问这个问题,因为我可能做错了什么。以下代码将尝试测试any() 函数与循环使用的性能。

#!/usr/bin/python3
#

import time
from unicodedata import normalize


file_path='./tests'


start=time.time()
with open(file_path, encoding='utf-8', mode='rt') as f:
    tests_list=f.read()
print('File reading done in {} seconds'.format(time.time() - start))

start=time.time()
tests_list=[line.strip() for line in normalize('NFC',tests_list).splitlines()]
print('String formalization, and list strip done in {} seconds'.format(time.time()-start))
print('{} strings'.format(len(tests_list)))


unallowed_combinations=['ab','ac','ad','ae','af','ag','ah','ai','af','ax',
                        'ae','rt','rz','bt','du','iz','ip','uy','io','ik',
                        'il','iw','ww','wp']


def combination_is_valid(string):
    if any(combination in string for combination in unallowed_combinations):
        return False

    return True


def combination_is_valid2(string):
    for combination in unallowed_combinations:
        if combination in string:
            return False

    return True


print('Testing the performance of any()')

start=time.time()
for string in tests_list:
    combination_is_valid(string)
print('combination_is_valid ended in {} seconds'.format(time.time()-start))


start=time.time()
for string in tests_list:
    combination_is_valid2(string)
print('combination_is_valid2 ended in {} seconds'.format(time.time()-start))  

前面的代码很能代表我所做的测试,如果我们看一下结果:

File reading done in 0.22988605499267578 seconds
String formalization, and list strip done in 6.803032875061035 seconds
38709922 strings
Testing the performance of any()
combination_is_valid ended in 80.74802565574646 seconds
combination_is_valid2 ended in 41.69514226913452 seconds


File reading done in 0.24268722534179688 seconds
String formalization, and list strip done in 6.720442771911621 seconds
38709922 strings
Testing the performance of any()
combination_is_valid ended in 79.05265760421753 seconds
combination_is_valid2 ended in 42.24800777435303 seconds

我发现使用循环比使用any() 快一半,这有点令人惊讶。对此有何解释?我是不是做错了什么?

(我在GNU-Linux下使用python3.4)

【问题讨论】:

  • 您的测试向量是否包含任何会返回 True 的字符串?
  • 这可能是因为生成器表达式在循环上提供了一定程度的间接性,这会减慢速度。
  • 关于你所说的循环提前退出:any 也提前退出(只迭代直到一个真正的值),所以这没有区别。

标签: python performance python-3.x


【解决方案1】:

其实any()函数等于下面的函数:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

这就像你的第二个函数,但是由于 any() 本身返回一个布尔值,你不需要检查结果然后返回一个新值,所以性能的差异是因为你实际上是使用了冗余返回和if 条件,还在另一个函数中调用了any

所以any 的优点是你不需要用另一个函数包装它,因为它会为你做所有事情。

正如@interjay 在评论中提到的,我错过的最重要原因似乎是您将生成器表达式传递给any(),它不会立即提供结果,因为它会按需生成结果做额外的工作。

基于PEP 0289 -- Generator Expressions

生成器表达式的语义等价于创建匿名生成器函数并调用它。例如:

g = (x**2 for x in range(10))
print g.next()

相当于:

def __gen(exp):
    for x in exp:
        yield x**2
g = __gen(iter(range(10)))
print g.next()

所以你可以看到,每次 python 想要访问下一个项目时,它都会调用 iter 函数和生成器的 next 方法。最后的结果是在这种情况下使用 any() 有点过头了案例。

【讨论】:

  • 与循环相比,单个附加 if 的影响可以忽略不计。更大的区别是any 版本使用了生成器表达式。
  • @interjay 这不是因为if,因为我说这是因为额外的函数调用和条件。但是生成器表达式也会是一个原因,但不多。
  • 循环做了很多工作,寻找多个子字符串。额外的函数调用和if 不太可能将其花费的时间加倍。
  • @interjay 是的。我懂了。感谢您的关注,我只是根据您的提示更新答案。
  • 通过使用生成器表达式,any 必须使用一个额外的函数调用每个元素。所以我觉得@interjay 就在这里。
【解决方案2】:

既然你的真正问题得到了回答,我就对隐含的问题试一试:

您只需执行unallowed_combinations = sorted(set(unallowed_combinations)) 即可免费获得速度提升,因为它包含重复项。

鉴于此,我所知道的最快的方法是

valid3_re = re.compile("|".join(map(re.escape, unallowed_combinations)))

def combination_is_valid3(string):
    return not valid3_re.search(string)

使用 CPython 3.5,对于一些行长为 60 个字符的测试数据,

combination_is_valid ended in 3.3051061630249023 seconds
combination_is_valid2 ended in 2.216959238052368 seconds
combination_is_valid3 ended in 1.4767844676971436 seconds

第三个是正则表达式版本,在 PyPy3 上我得到

combination_is_valid ended in 2.2926249504089355 seconds
combination_is_valid2 ended in 2.0935239791870117 seconds
combination_is_valid3 ended in 0.14300894737243652 seconds

FWIW,这与 Rust(一种低级语言,如 C++)具有竞争力,实际上在正则表达式方面明显胜出。与 CPython 相比,较短的字符串更偏爱 PyPy(例如,4x CPython,行长为 10),因为开销更重要。

由于只有大约三分之一的 CPython 正则表达式运行时是循环开销,因此我们得出结论,PyPy 的正则表达式实现更适合此用例。我建议看看是否有 CPython 正则表达式实现可以与 PyPy 竞争。

【讨论】:

  • unallowed_combinations 列表上的重复值是我在输入测试时犯的一个错误,但非常感谢您的回答!我的基准是...22.354313850402832 seconds
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-02
  • 1970-01-01
  • 2018-03-09
  • 1970-01-01
  • 2013-12-08
  • 1970-01-01
相关资源
最近更新 更多