Python isPalindrome 之间的时间比较

Time Comparison between Python isPalindrome

我在python中写了相同时间复杂度3的isPalindrome函数。但令人惊讶的是,他们的表现却大不相同。

def isPalindrome(value):
    i, j = 0, len(value) - 1
    while i < j and value[i] == value[j]:
         i, j = i + 1, j - 1
    return i >= j

def isPalindrome2(value):
    return value == value[::-1]

def isPalindrome3(value):
    res = []
    for c in value:
       res.append(c)
    return value == ''.join(res)

然后我用timeit模块测试一下

import timeit
timeit.timeit(lambda: isPalindrome('abcdefghijklmnopqrstuvzzavutsrqponmlkjihgfedcba'), number=1000000)
timeit.timeit(lambda: isPalindrome2('abcdefghijklmnopqrstuvzzavutsrqponmlkjihgfedcba'), number=1000000)
timeit.timeit(lambda: isPalindrome3('abcdefghijklmnopqrstuvzzavutsrqponmlkjihgfedcba'), number=1000000)

输出:

我试图找到为什么方法 2 花费的时间这么少,但找不到任何解释。请帮助我理解为什么,这将非常有帮助。

可能在第二种情况下,切片是由在内存中低级别工作的C代码执行的。 在第一种情况下,您需要一个一个地获取元素。 在第三个中,您需要迭代一个迭代器(由 for...in 语法创建)并且您需要执行需要计算和检查的追加,所有这些都用 python 代码编写。

要找出为什么某个函数在 Python 中更快/更慢的原因,请始终使用 dis.dis。标准 dis 模块中的 dis 函数可以反汇编函数编译成的字节码,因此您可以看到当您 运行 它时真正发生了什么。

下面是三个函数的反汇编:

import dis
dis.dis(isPalindrome)
  2           0 LOAD_CONST               1 (0)
              2 LOAD_GLOBAL              0 (len)
              4 LOAD_FAST                0 (value)
              6 CALL_FUNCTION            1
              8 LOAD_CONST               2 (1)
             10 BINARY_SUBTRACT
             12 ROT_TWO
             14 STORE_FAST               1 (i)
             16 STORE_FAST               2 (j)

  3          18 LOAD_FAST                1 (i)
             20 LOAD_FAST                2 (j)
             22 COMPARE_OP               0 (<)
             24 POP_JUMP_IF_FALSE       42 (to 84)
             26 LOAD_FAST                0 (value)
             28 LOAD_FAST                1 (i)
             30 BINARY_SUBSCR
             32 LOAD_FAST                0 (value)
             34 LOAD_FAST                2 (j)
             36 BINARY_SUBSCR
             38 COMPARE_OP               2 (==)
             40 POP_JUMP_IF_FALSE       42 (to 84)

  4     >>   42 LOAD_FAST                1 (i)
             44 LOAD_CONST               2 (1)
             46 BINARY_ADD
             48 LOAD_FAST                2 (j)
             50 LOAD_CONST               2 (1)
             52 BINARY_SUBTRACT
             54 ROT_TWO
             56 STORE_FAST               1 (i)
             58 STORE_FAST               2 (j)

  3          60 LOAD_FAST                1 (i)
             62 LOAD_FAST                2 (j)
             64 COMPARE_OP               0 (<)
             66 POP_JUMP_IF_FALSE       42 (to 84)
             68 LOAD_FAST                0 (value)
             70 LOAD_FAST                1 (i)
             72 BINARY_SUBSCR
             74 LOAD_FAST                0 (value)
             76 LOAD_FAST                2 (j)
             78 BINARY_SUBSCR
             80 COMPARE_OP               2 (==)
             82 POP_JUMP_IF_TRUE        21 (to 42)

  5     >>   84 LOAD_FAST                1 (i)
             86 LOAD_FAST                2 (j)
             88 COMPARE_OP               5 (>=)
             90 RETURN_VALUE
dis.dis(isPalindrome2)
  2           0 LOAD_FAST                0 (value)
              2 LOAD_FAST                0 (value)
              4 LOAD_CONST               0 (None)
              6 LOAD_CONST               0 (None)
              8 LOAD_CONST               1 (-1)
             10 BUILD_SLICE              3
             12 BINARY_SUBSCR
             14 COMPARE_OP               2 (==)
             16 RETURN_VALUE
dis.dis(isPalindrome3)
  2           0 BUILD_LIST               0
              2 STORE_FAST               1 (res)

  3           4 LOAD_FAST                0 (value)
              6 GET_ITER
        >>    8 FOR_ITER                 7 (to 24)
             10 STORE_FAST               2 (c)

  4          12 LOAD_FAST                1 (res)
             14 LOAD_METHOD              0 (append)
             16 LOAD_FAST                2 (c)
             18 CALL_METHOD              1
             20 POP_TOP
             22 JUMP_ABSOLUTE            4 (to 8)

  5     >>   24 LOAD_FAST                0 (value)
             26 LOAD_CONST               1 ('')
             28 LOAD_METHOD              1 (join)
             30 LOAD_FAST                1 (res)
             32 CALL_METHOD              1
             34 COMPARE_OP               2 (==)
             36 RETURN_VALUE

如您所见,isPalindrome2 编译的字节码比其他两个都少很多。这是因为它的大部分操作都是内置于 Python 中的,因此是用 C 编写的,而不是 Python。 isPalindrome2 中每个操作背后的 C 代码可能与其他函数之一中的 Python 代码做了类似的事情(尽管很难说),但它更快只是因为 C是一种非常快的语言,Python 非常慢。

isPalindrome2 字节码的第 4-13 字节基本上与 isPalindrome3 字节码的第 0-33 字节做同样的事情,但是 isPalindrome2 使用了很多由C 代码,而 isPalindrome3 调用 Python 函数(执行起来非常慢),额外加载常量/变量 3 次,并使用 Python for 循环。这些东西都比它们的 C 等价物慢,这意味着它更慢。第一个功能也是如此。因此,字节码少得多,主要使用C代码的第二个函数速度要快得多。