[SOLVED] Why is this method of testing for palindromes so much slower than using [::-1]?

Issue

I have two different methods of testing for a palindrome. One is the following:

def palindrome(text):
    return text == text[::-1]

Very simple, of course, but I had imagined it would be slow as it (surely) has to store the value of text[::-1] somewhere after reversing it, then it checks every character in both. So, I attempted another method:

def palindrome_2(text):
    left = 0
    right = len(text) - 1
    while left < right:
        if text[left] != text[right]:
            return False
        right -= 1
        left += 1
    return True

It starts at the start and end points, and works its way into the center. This should, as far as I can tell, be faster, since it only checks [0, n // 2) and vice versa for the end. However, when I use timeit to test these, the first is 0.32 and the second is 1.34. Why?

Solution

I think it is informative to use dis to have a look at the the bytecode produced here:

import dis
dis.dis(palindrome)
dis.dis(palindrome_2)

Palindrome:

  4           0 LOAD_FAST                0 (text)
              3 LOAD_FAST                0 (text)
              6 LOAD_CONST               0 (None)
              9 LOAD_CONST               0 (None)
             12 LOAD_CONST               2 (-1)
             15 BUILD_SLICE              3
             18 BINARY_SUBSCR
             19 COMPARE_OP               2 (==)
             22 RETURN_VALUE

Palindrome_2:

 10           0 LOAD_CONST               1 (0)
              3 STORE_FAST               1 (left)

 11           6 LOAD_GLOBAL              0 (len)
              9 LOAD_FAST                0 (text)
             12 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             15 LOAD_CONST               2 (1)
             18 BINARY_SUBTRACT
             19 STORE_FAST               2 (right)

 12          22 SETUP_LOOP              60 (to 85)
        >>   25 LOAD_FAST                1 (left)
             28 LOAD_FAST                2 (right)
             31 COMPARE_OP               0 (<)
             34 POP_JUMP_IF_FALSE       84

 13          37 LOAD_FAST                0 (text)
             40 LOAD_FAST                1 (left)
             43 BINARY_SUBSCR
             44 LOAD_FAST                0 (text)
             47 LOAD_FAST                2 (right)
             50 BINARY_SUBSCR
             51 COMPARE_OP               3 (!=)
             54 POP_JUMP_IF_FALSE       61

 14          57 LOAD_CONST               3 (False)
             60 RETURN_VALUE

 15     >>   61 LOAD_FAST                2 (right)
             64 LOAD_CONST               2 (1)
             67 INPLACE_SUBTRACT
             68 STORE_FAST               2 (right)

 16          71 LOAD_FAST                1 (left)
             74 LOAD_CONST               2 (1)
             77 INPLACE_ADD
             78 STORE_FAST               1 (left)
             81 JUMP_ABSOLUTE           25
        >>   84 POP_BLOCK

 17     >>   85 LOAD_CONST               4 (True)
             88 RETURN_VALUE

Essentially when doing the same amount of work C code will be faster than the corresponding python code.
As you can see the first approach calls a built in function, which is written in fast C. The second function has to do more of the work in python code, including handling the looping construct overhead, which will be slower than the call to the C.

Answered By – shuttle87

Answer Checked By – Clifford M. (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *