【问题标题】:Comparing the speed of startswith() .vs. in() [duplicate]比较startswith() .vs的速度。一式两份]
【发布时间】:2017-11-15 03:42:01
【问题描述】:

我的印象是 startswith 必须比 in 更快,原因很简单,in 必须进行更多检查 (允许查看单词for 位于字符串中的任何位置)。但我有疑问,所以我决定timeit。下面给出了计时代码,您可能会注意到我没有做太多计时;代码比较简单。

import timeit

setup1='''
def in_test(sent, word):
    if word in sent:
        return True
    else:
        return False
'''

setup2='''
def startswith_test(sent, word):
    if sent.startswith(word):
        return True
    else:
        return False
'''

print(timeit.timeit('in_test("this is a standard sentence", "this")', setup=setup1))
print(timeit.timeit('startswith_test("this is a standard sentence", "this")', setup=setup2))

结果:

>> in:         0.11912814951705597
>> startswith: 0.22812353561129417

所以startswith 的速度是原来的两倍!.. 鉴于我在上面所说的,我发现这种行为非常令人费解。我在计时两者时做错了什么还是in确实更快?如果有,为什么?

请注意,即使它们都返回False,结果也非常相似(在这种情况下,in 必须实际遍历整个句子,以防它之前只是短路) :

print(timeit.timeit('in_test("another standard sentence, would be that", "this")', setup=setup1))
print(timeit.timeit('startswith_test("another standard sentence, would be that", "this")', setup=setup2))

>> in:         0.12854891578786237
>> startswith: 0.2233201940338861

如果必须从头开始实现这两个函数,它看起来像这样(伪代码):

startswith:开始将 word 的字母与 sentence 的字母一一进行比较,直到 a) word 被耗尽(返回True) 或 b) 检查返回 False(返回 False)

in:在句子中可以找到单词首字母的每个位置调用startswith

我只是不明白..


澄清一下,instartswith 不等价;我只是在谈论一个人试图找到的单词必须是字符串中的 first 的情况。

【问题讨论】:

    标签: python performance python-3.x time


    【解决方案1】:

    这是因为您必须查找并调用一个方法。 in 是专门的,直接通向COMPARE_OP(调用cmp_outcome,而PySequence_Contains 又调用PySequence_Contains),而str.startswith 通过较慢的字节码:

    2 LOAD_ATTR                0 (startswith)
    4 LOAD_FAST                1 (word)
    6 CALL_FUNCTION            1              # the slow part
    

    in 替换为__contains__,也强制针对这种情况调用函数,几乎可以消除速度差异:

    setup1='''
    def in_test(sent, word):
        if sent.__contains__(word):
            return True
        else:
            return False
    '''
    

    还有,时间安排:

    print(timeit.timeit('in_test("this is a standard sentence", "this")', setup=setup1))
    print(timeit.timeit('startswith_test("this is a standard sentence", "this")', setup=setup2))
    0.43849368393421173
    0.4993997460696846
    

    in 之所以在这里获胜,是因为它不需要完成整个函数调用设置,而且它提供了有利的案例。

    【讨论】:

    • 有利的情况下似乎没有这样的影响;悲观的情况产生了同样的结果。更像是字符串的长度和startswith的开销
    • 我应该更详细@Ev.Kounis :-) 长度就是我所说的有利情况。小字符串中是否匹配的事实不会有太大的区别。
    • 现在我们又回到了同一个页面 xD
    【解决方案2】:

    您正在比较字符串上的运算符-vs- 属性查找和函数调用。即使第一个需要很长时间处理大量数据,第二个也会有更高的开销。

    此外,您正在查找第一个单词,因此如果匹配,in 将查看与 startswith() 一样多的数据。要查看差异,您应该查看一个悲观的情况(未找到结果,或在字符串末尾匹配):

    setup1='''
    data = "xxxx"*1000
    def ....
    
    print(timeit.timeit('in_test(data, "this")', setup=setup1))
    0.932795189000899
    print(timeit.timeit('startswith_test(data, "this")', setup=setup2))
    0.22242475600069156
    

    【讨论】:

    • 感谢您的回答。悲观的案例已经在编辑的帖子上。
    • 你的悲观案例很短。查看 4 个字符 -vs- 40 只是一个舍入错误。查看 4 -vs- 4000 是您开始看到差异的地方。
    • 我明白,但我想使用其中任何一个来检查这样的小句子,而不是整个文本。但现在它确实更有意义了。
    • 所以我们可以说in 是 O(n) 而startswith 是 O(c),但是对于小的 ns O(n) 更好,对吧?
    • 正是 :) 就是这样
    【解决方案3】:

    如果您查看函数生成的字节码:

    >>> dis.dis(in_test)
      2           0 LOAD_FAST                1 (word)
                  3 LOAD_FAST                0 (sent)
                  6 COMPARE_OP               6 (in)
                  9 POP_JUMP_IF_FALSE       16
    
      3          12 LOAD_CONST               1 (True)
                 15 RETURN_VALUE
    
      5     >>   16 LOAD_CONST               2 (False)
                 19 RETURN_VALUE
                 20 LOAD_CONST               0 (None)
                 23 RETURN_VALUE
    

    您会注意到有很多开销与字符串匹配没有直接关系。对更简单的函数进行测试:

    def in_test(sent, word):
        return word in sent
    

    会更可靠。

    【讨论】:

      猜你喜欢
      • 2020-08-06
      • 1970-01-01
      • 1970-01-01
      • 2019-03-07
      • 1970-01-01
      • 2011-10-21
      • 2020-05-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多