This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: chained comparisons slower than using `and`
Type: performance Stage: resolved
Components: Interpreter Core Versions: Python 3.10
process
Status: closed Resolution: duplicate
Dependencies: Superseder: Using multiple comparison operators can cause performance issues
View: 45542
Assigned To: Nosy List: Cezary.Wagner, Dennis Sweeney
Priority: normal Keywords:

Created on 2022-04-04 22:00 by Cezary.Wagner, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (4)
msg416699 - (view) Author: Cezary Wagner (Cezary.Wagner) Date: 2022-04-04 22:00
I am experienced programmer 10y+ - that is very very strange performance problem when I play Python timeit with my son :)

three way operator a <= x <= b is slower than a <= x and x <= b.

It looks like wrong implementation since it is impossible that two separate check is faster that one check (with two low level check in C).




import timeit

REPEATS = 100


def test1():
    selected = []
    for i in range(REPEATS):
        if i >= 25 and i <= 75:
            selected.append(i)
    return selected


def test2():
    selected = []
    for i in range(REPEATS):
        if 25 <= i <= 75:
            selected.append(i)
    return selected


print(timeit.timeit(test1))
print(timeit.timeit(test2))


Result is on Windows 10.
4.428947699998389
4.9062477999978
msg416701 - (view) Author: Cezary Wagner (Cezary.Wagner) Date: 2022-04-04 22:21
Some more experiments:

import dis
import timeit

REPEATS = 100


def test1():
    selected = []
    for i in range(REPEATS):
        if i >= 25 and i <= 75:
            selected.append(i)
    return selected


def test2():
    selected = []
    for i in range(REPEATS):
        if 25 <= i <= 75:
            selected.append(i)
    return selected


def test3():
    return [x for x in range(REPEATS) if x >= 25 and x <= 75]


def test4():
    return [x for x in range(REPEATS) if 25 <= x <= 75]


def test(f):
    print(dis.dis(f.__code__.co_code))
    print(timeit.timeit(f))


test(test1)
test(test2)
test(test3)
test(test4)

Result:

          0 BUILD_LIST               0
          2 STORE_FAST               0 (0)
          4 LOAD_GLOBAL              0 (0)
          6 LOAD_GLOBAL              1 (1)
          8 CALL_FUNCTION            1
         10 GET_ITER
    >>   12 FOR_ITER                15 (to 44)
         14 STORE_FAST               1 (1)
         16 LOAD_FAST                1 (1)
         18 LOAD_CONST               1 (1)
         20 COMPARE_OP               5 (>=)
         22 POP_JUMP_IF_FALSE       21 (to 42)
         24 LOAD_FAST                1 (1)
         26 LOAD_CONST               2 (2)
         28 COMPARE_OP               1 (<=)
         30 POP_JUMP_IF_FALSE       21 (to 42)
         32 LOAD_FAST                0 (0)
         34 LOAD_METHOD              2 (2)
         36 LOAD_FAST                1 (1)
         38 CALL_METHOD              1
         40 POP_TOP
    >>   42 JUMP_ABSOLUTE            6 (to 12)
    >>   44 LOAD_FAST                0 (0)
         46 RETURN_VALUE
None
4.565677999999025
          0 BUILD_LIST               0
          2 STORE_FAST               0 (0)
          4 LOAD_GLOBAL              0 (0)
          6 LOAD_GLOBAL              1 (1)
          8 CALL_FUNCTION            1
         10 GET_ITER
    >>   12 FOR_ITER                19 (to 52)
         14 STORE_FAST               1 (1)
         16 LOAD_CONST               1 (1)
         18 LOAD_FAST                1 (1)
         20 DUP_TOP
         22 ROT_THREE
         24 COMPARE_OP               1 (<=)
         26 POP_JUMP_IF_FALSE       18 (to 36)
         28 LOAD_CONST               2 (2)
         30 COMPARE_OP               1 (<=)
         32 POP_JUMP_IF_FALSE       25 (to 50)
         34 JUMP_FORWARD             2 (to 40)
    >>   36 POP_TOP
         38 JUMP_ABSOLUTE            6 (to 12)
    >>   40 LOAD_FAST                0 (0)
         42 LOAD_METHOD              2 (2)
         44 LOAD_FAST                1 (1)
         46 CALL_METHOD              1
         48 POP_TOP
    >>   50 JUMP_ABSOLUTE            6 (to 12)
    >>   52 LOAD_FAST                0 (0)
         54 RETURN_VALUE
None
5.6398234000007506
          0 LOAD_CONST               1 (1)
          2 LOAD_CONST               2 (2)
          4 MAKE_FUNCTION            0
          6 LOAD_GLOBAL              0 (0)
          8 LOAD_GLOBAL              1 (1)
         10 CALL_FUNCTION            1
         12 GET_ITER
         14 CALL_FUNCTION            1
         16 RETURN_VALUE
None
3.8792907999959425
          0 LOAD_CONST               1 (1)
          2 LOAD_CONST               2 (2)
          4 MAKE_FUNCTION            0
          6 LOAD_GLOBAL              0 (0)
          8 LOAD_GLOBAL              1 (1)
         10 CALL_FUNCTION            1
         12 GET_ITER
         14 CALL_FUNCTION            1
         16 RETURN_VALUE
None
3.8591266999937943
msg416704 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python committer) Date: 2022-04-04 23:01
I believe this is a duplicate of this issue: https://bugs.python.org/issue45542
msg416705 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python committer) Date: 2022-04-04 23:10
Feel free to comment on that issue if you have any ideas about how to address the concerns there.
History
Date User Action Args
2022-04-11 14:59:58adminsetgithub: 91377
2022-04-04 23:10:33Dennis Sweeneysetstatus: open -> closed
superseder: Using multiple comparison operators can cause performance issues
messages: + msg416705

resolution: duplicate
stage: resolved
2022-04-04 23:04:38Dennis Sweeneysettitle: Bug or bad performance -> chained comparisons slower than using `and`
2022-04-04 23:01:47Dennis Sweeneysetnosy: + Dennis Sweeney
messages: + msg416704
2022-04-04 22:21:34Cezary.Wagnersetmessages: + msg416701
2022-04-04 22:00:20Cezary.Wagnercreate