Without further ado, let’s take a look at an example:
import dis, timeit
a = 'lctrl'
def match_case_ors():
match a:
case 'ctrl' | 'lctrl' | 'rctrl': ...
case _: ...
def if_contains():
if a in {'ctrl', 'lctrl', 'rctrl'}: ...
else: ...
dis.dis(match_case_ors)
print('-' * 84)
dis.dis(if_contains)
match_case = timeit.timeit(match_case_ors)
if_stmt = timeit.timeit(if_contains)
print('\n'
f'{match_case = :g}\n'
f'{if_stmt = :g}\n'
f'speedup {1. - if_stmt / match_case:.1%}'
)
We can see that if is ~15-30% (and higher) faster. And the more elements, the faster if works; while the pattern matching gets slower and slower.
The solution is simple. Instead of
9 14 COPY 1
16 LOAD_CONST 1 ('ctrl')
18 COMPARE_OP 2 (==)
24 POP_JUMP_IF_FALSE 3 (to 32)
26 POP_TOP
10 28 LOAD_CONST 0 (None)
30 RETURN_VALUE
9 >> 32 COPY 1
34 LOAD_CONST 2 ('lctrl')
36 COMPARE_OP 2 (==)
42 POP_JUMP_IF_FALSE 3 (to 50)
44 POP_TOP
10 46 LOAD_CONST 0 (None)
48 RETURN_VALUE
9 >> 50 COPY 1
52 LOAD_CONST 3 ('rctrl')
54 COMPARE_OP 2 (==)
60 POP_JUMP_IF_FALSE 3 (to 68)
62 POP_TOP
10 64 LOAD_CONST 0 (None)
66 RETURN_VALUE
Generate bytecode like:
16 2 LOAD_GLOBAL 0 (a)
14 LOAD_CONST 1 (frozenset({'ctrl', 'lctrl', 'rctrl'}))
16 CONTAINS_OP 0
18 POP_JUMP_IF_FALSE 2 (to 24)
17 20 LOAD_CONST 0 (None)
22 RETURN_VALUE
In such cases, it is the use of a set or a frozen set that will give such an increase, otherwise it is not significant.
And since the behavior of the set in this case is predictable and does exactly what we need, it seems to me a good idea to implicitly create a set in such pattern matching.
If someone does not like this option, there is a less optimized, but quite obvious solution.
Instead of POP_JUMP_IF_FALSE, use the opposite POP_JUMP_IF_TRUE, just like lazy or does.
16 2 LOAD_GLOBAL 0 (a)
14 LOAD_CONST 1 ('ctrl')
16 COMPARE_OP 2 (==)
22 POP_JUMP_IF_TRUE 22 (to 68)
24 LOAD_GLOBAL 0 (a)
36 LOAD_CONST 2 ('lctrl')
38 COMPARE_OP 2 (==)
44 POP_JUMP_IF_TRUE 11 (to 68)
46 LOAD_GLOBAL 0 (a)
58 LOAD_CONST 3 ('rctrl')
60 COMPARE_OP 2 (==)
66 POP_JUMP_IF_FALSE 2 (to 72)
18 >> 68 LOAD_CONST 0 (None)
70 RETURN_VALUE
Otherwise, using if in such cases is more performant, but not as elegant as pattern matching.
This is my first thread, so I may have misunderstood something. Also, my native language is Russian, not English.
Thanks.