Skip to content

gh-138912: Speedup match class patterns with only keyword sub-patterns - #158303

Open
cdce8p wants to merge 3 commits into
python:mainfrom
cdce8p:match-class-perf
Open

cdce8p wants to merge 3 commits into
python:mainfrom
cdce8p:match-class-perf

Conversation

@cdce8p

@cdce8p cdce8p commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

If a match class pattern only contains keywords, it's not necessary to construct a temporary tuple with all the results from the PyObject_GetOptionalAttr calls before unpacking it again. Evaluating the keywords directly can provide a significant performance improvement, especially for patterns with just a few keywords (< 3) on the order of 20-30%. For 5 keywords it's closer to 10%.

Adding a dedicated opcode MATCH_CLASS_GET_OPT_ATTR also unlocks the ability for further improvements through specialization later on, similarly to LOAD_ATTR. Testing with specialization disabled suggests that it might even be possible to get as fast or slightly fast than comparable if statements, even for a larger number of keywords.

--
This PR does change the execution order slightly. Whereas currently a missing attribute on an instance would fail the pattern immediately before any sub-patterns are evaluated, with the change here each attribute and pattern will be evaluated together one after another. In practice this is highly unlikely to change results though.

Arguably the "new" behavior is the expected one based on the docs:

If only keyword patterns are present, they are processed as follows, one by one:

  1. The keyword is looked up as an attribute on the subject.
    • If this raises an exception other than AttributeError, the exception bubbles up.
    • If this raises AttributeError, the class pattern has failed.
    • Else, the subpattern associated with the keyword pattern is matched against the subject’s attribute value. If this fails, the class pattern fails; if this succeeds, the match proceeds to the next keyword.
  2. If all keyword patterns succeed, the class pattern succeeds.

Though it should be noted that PEP 634 explicitly called out that part as undefined behavior a user should not rely on:

The only side-effect produced explicitly by the matching process is the binding of names. However, the process relies on attribute access, instance checks, len(), equality and item access on the subject and some of its components. It also evaluates value patterns and the class name of class patterns. While none of those typically create any side-effects, in theory they could. This proposal intentionally leaves out any specification of what methods are called or how many times. This behavior is therefore undefined and user code should not rely on it.

--

Micro benchmark (only keywords)

from timeit import timeit
setup = """
class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y
a = A(1, 2)

def f1(a):
    if isinstance(a, A) and a.x == 1 and a.y == 2:
        return True
    return False

def f2(a):
    match a:
        case A(x=1, y=2):
            return True
    return False
"""
print(f"If\t{round(timeit("f1(a)", setup, number=10**7), 4)}")
print(f"Match\t{round(timeit("f2(a)", setup, number=10**7), 4)}")
# Before
If      0.4035
Match   0.7492

# After
Match   0.5709 -23.80%

@read-the-docs-community

read-the-docs-community Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34792660 | 📁 Comparing 9f38f61 against main (1c4fa4f)

  🔍 Preview build  

2 files changed
± library/dis.html
± whatsnew/changelog.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant