Skip to content

Commit 972cfaa

Browse files
cdce8peendebakpt
andauthored
gh-138912: Add fast path for match class patterns without sub-patterns (#144820)
Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
1 parent 5eb4ab5 commit 972cfaa

6 files changed

Lines changed: 63 additions & 26 deletions

File tree

‎Doc/library/dis.rst‎

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,32 +1899,38 @@ iterations of the loop.
18991899

19001900
The operand determines which intrinsic function is called:
19011901

1902-
+------------------------------------------+-----------------------------------+
1903-
| Operand | Description |
1904-
+==========================================+===================================+
1905-
| ``INTRINSIC_2_INVALID`` | Not valid |
1906-
+------------------------------------------+-----------------------------------+
1907-
| ``INTRINSIC_PREP_RERAISE_STAR`` | Calculates the |
1908-
| | :exc:`ExceptionGroup` to raise |
1909-
| | from a ``try-except*``. |
1910-
+------------------------------------------+-----------------------------------+
1911-
| ``INTRINSIC_TYPEVAR_WITH_BOUND`` | Creates a :class:`typing.TypeVar` |
1912-
| | with a bound. |
1913-
+------------------------------------------+-----------------------------------+
1914-
| ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS`` | Creates a |
1915-
| | :class:`typing.TypeVar` with |
1916-
| | constraints. |
1917-
+------------------------------------------+-----------------------------------+
1918-
| ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS`` | Sets the ``__type_params__`` |
1919-
| | attribute of a function. |
1920-
+------------------------------------------+-----------------------------------+
1921-
| ``INTRINSIC_ADD_CONDITIONAL_ANNOTATION`` | Adds an annotation index to the |
1922-
| | ``__conditional_annotations__`` |
1923-
| | set. |
1924-
+------------------------------------------+-----------------------------------+
1902+
+------------------------------------------+-----------------------------------------+
1903+
| Operand | Description |
1904+
+==========================================+=========================================+
1905+
| ``INTRINSIC_2_INVALID`` | Not valid |
1906+
+------------------------------------------+-----------------------------------------+
1907+
| ``INTRINSIC_PREP_RERAISE_STAR`` | Calculates the |
1908+
| | :exc:`ExceptionGroup` to raise |
1909+
| | from a ``try-except*``. |
1910+
+------------------------------------------+-----------------------------------------+
1911+
| ``INTRINSIC_TYPEVAR_WITH_BOUND`` | Creates a :class:`typing.TypeVar` |
1912+
| | with a bound. |
1913+
+------------------------------------------+-----------------------------------------+
1914+
| ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS`` | Creates a |
1915+
| | :class:`typing.TypeVar` with |
1916+
| | constraints. |
1917+
+------------------------------------------+-----------------------------------------+
1918+
| ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS`` | Sets the ``__type_params__`` |
1919+
| | attribute of a function. |
1920+
+------------------------------------------+-----------------------------------------+
1921+
| ``INTRINSIC_ADD_CONDITIONAL_ANNOTATION`` | Adds an annotation index to the |
1922+
| | ``__conditional_annotations__`` |
1923+
| | set. |
1924+
+------------------------------------------+-----------------------------------------+
1925+
| ``INTRINSIC_MATCH_CLASS_ISINSTANCE`` | Do :func:`isinstance` checks for |
1926+
| | :ref:`Class patterns <class-patterns>`. |
1927+
+------------------------------------------+-----------------------------------------+
19251928

19261929
.. versionadded:: 3.12
19271930

1931+
.. versionchanged:: 3.16
1932+
Added ``INTRINSIC_MATCH_CLASS_ISINSTANCE``.
1933+
19281934

19291935
.. opcode:: LOAD_SPECIAL
19301936

‎Include/internal/pycore_intrinsics.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
#define INTRINSIC_SET_FUNCTION_TYPE_PARAMS 4
3232
#define INTRINSIC_SET_TYPEPARAM_DEFAULT 5
3333
#define INTRINSIC_ADD_CONDITIONAL_ANNOTATION 6
34+
#define INTRINSIC_MATCH_CLASS_ISINSTANCE 7
3435

35-
#define MAX_INTRINSIC_2 6
36+
#define MAX_INTRINSIC_2 7
3637

3738
typedef PyObject *(*intrinsic_func1)(PyThreadState* tstate, PyObject *value);
3839
typedef PyObject *(*intrinsic_func2)(PyThreadState* tstate, PyObject *value1, PyObject *value2);

‎Lib/test/test_capi/test_opt.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5752,12 +5752,13 @@ def testfunc(n):
57525752
def test_match_class(self):
57535753
def testfunc(n):
57545754
class A:
5755+
__match_args__ = ("val",)
57555756
val = 1
57565757
x = A()
57575758
ret = 0
57585759
for _ in range(n):
57595760
match x:
5760-
case A():
5761+
case A(1):
57615762
ret += x.val
57625763
return ret
57635764

@@ -5766,7 +5767,7 @@ class A:
57665767
uops = get_opnames(ex)
57675768

57685769
self.assertIn("_MATCH_CLASS", uops)
5769-
self.assertEqual(count_ops(ex, "_POP_TOP_NOP"), 4)
5770+
self.assertEqual(count_ops(ex, "_POP_TOP_NOP"), 5)
57705771

57715772
def test_dict_update(self):
57725773
def testfunc(n):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add fast path for :keyword:`match` class patterns without any sub-patterns
2+
to improve performance by ~15%.

‎Python/codegen.c‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6245,6 +6245,13 @@ codegen_pattern_class(compiler *c, pattern_ty p, pattern_context *pc)
62456245
if (nattrs) {
62466246
RETURN_IF_ERROR(validate_kwd_attrs(c, kwd_attrs, kwd_patterns));
62476247
}
6248+
if (nargs + nattrs == 0) {
6249+
// Fast path if there are no sub-patterns
6250+
VISIT(c, expr, p->v.MatchClass.cls);
6251+
ADDOP_I(c, LOC(p), CALL_INTRINSIC_2, INTRINSIC_MATCH_CLASS_ISINSTANCE);
6252+
RETURN_IF_ERROR(jump_to_fail_pop(c, LOC(p), pc, POP_JUMP_IF_FALSE));
6253+
return SUCCESS;
6254+
}
62486255
VISIT(c, expr, p->v.MatchClass.cls);
62496256
PyObject *attr_names = PyTuple_New(nattrs);
62506257
if (attr_names == NULL) {

‎Python/intrinsics.c‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,25 @@ add_conditional_annotation(PyThreadState* tstate, PyObject *conditional_annotati
287287
Py_RETURN_NONE;
288288
}
289289

290+
static PyObject *
291+
match_class_isinstance(PyThreadState* tstate, PyObject *subject, PyObject *type)
292+
{
293+
/* Fast path for class patterns with no sub-patterns, e.g. `case C():`
294+
Equivalent to the isinstance check performed by _PyEval_MatchClass,
295+
including the same TypeError when the pattern does not refer to a
296+
class. */
297+
if (!PyType_Check(type)) {
298+
_PyErr_SetString(tstate, PyExc_TypeError,
299+
"class pattern must refer to a class");
300+
return NULL;
301+
}
302+
int res = PyObject_IsInstance(subject, type);
303+
if (res < 0) {
304+
return NULL;
305+
}
306+
return res ? Py_True : Py_False;
307+
}
308+
290309
const intrinsic_func2_info
291310
_PyIntrinsics_BinaryFunctions[] = {
292311
INTRINSIC_FUNC_ENTRY(INTRINSIC_2_INVALID, no_intrinsic2)
@@ -296,6 +315,7 @@ _PyIntrinsics_BinaryFunctions[] = {
296315
INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_FUNCTION_TYPE_PARAMS, _Py_set_function_type_params)
297316
INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_TYPEPARAM_DEFAULT, _Py_set_typeparam_default)
298317
INTRINSIC_FUNC_ENTRY(INTRINSIC_ADD_CONDITIONAL_ANNOTATION, add_conditional_annotation)
318+
INTRINSIC_FUNC_ENTRY(INTRINSIC_MATCH_CLASS_ISINSTANCE, match_class_isinstance)
299319
};
300320

301321
#undef INTRINSIC_FUNC_ENTRY

0 commit comments

Comments
 (0)