Skip to content

Commit 7c81286

Browse files
committed
gh-138912: Improve MATCH_CLASS opcode performance
1 parent cb07bd2 commit 7c81286

File tree

2 files changed

+32
-30
lines changed

2 files changed

+32
-30
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve :opcode:`MATCH_CLASS` performance. Patch by Marc Mueller

Python/ceval.c

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -709,15 +709,17 @@ match_class_attr(PyThreadState *tstate, PyObject *subject, PyObject *type,
709709
PyObject *name, PyObject *seen)
710710
{
711711
assert(PyUnicode_CheckExact(name));
712-
assert(PySet_CheckExact(seen));
713-
if (PySet_Contains(seen, name) || PySet_Add(seen, name)) {
714-
if (!_PyErr_Occurred(tstate)) {
715-
// Seen it before!
716-
_PyErr_Format(tstate, PyExc_TypeError,
717-
"%s() got multiple sub-patterns for attribute %R",
718-
((PyTypeObject*)type)->tp_name, name);
712+
if (seen != NULL) {
713+
assert(PySet_CheckExact(seen));
714+
if (PySet_Contains(seen, name) || PySet_Add(seen, name)) {
715+
if (!_PyErr_Occurred(tstate)) {
716+
// Seen it before!
717+
_PyErr_Format(tstate, PyExc_TypeError,
718+
"%s() got multiple sub-patterns for attribute %R",
719+
((PyTypeObject*)type)->tp_name, name);
720+
}
721+
return NULL;
719722
}
720-
return NULL;
721723
}
722724
PyObject *attr;
723725
(void)PyObject_GetOptionalAttr(subject, name, &attr);
@@ -740,14 +742,24 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type,
740742
if (PyObject_IsInstance(subject, type) <= 0) {
741743
return NULL;
742744
}
745+
// Short circuit if there aren't any arguments:
746+
Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwargs);
747+
Py_ssize_clean_t nattrs = nargs + nkwargs;
748+
if (!nattrs) {
749+
PyObject *attrs = PyTuple_New(0);
750+
return attrs;
751+
}
743752
// So far so good:
744-
PyObject *seen = PySet_New(NULL);
745-
if (seen == NULL) {
746-
return NULL;
753+
PyObject *seen = NULL;
754+
if (nattrs > 1) {
755+
seen = PySet_New(NULL);
756+
if (seen == NULL) {
757+
return NULL;
758+
}
747759
}
748-
PyObject *attrs = PyList_New(0);
760+
PyObject *attrs = PyTuple_New(nattrs);
749761
if (attrs == NULL) {
750-
Py_DECREF(seen);
762+
Py_XDECREF(seen);
751763
return NULL;
752764
}
753765
// NOTE: From this point on, goto fail on failure:
@@ -788,9 +800,7 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type,
788800
}
789801
if (match_self) {
790802
// Easy. Copy the subject itself, and move on to kwargs.
791-
if (PyList_Append(attrs, subject) < 0) {
792-
goto fail;
793-
}
803+
PyTuple_SET_ITEM(attrs, 0, subject);
794804
}
795805
else {
796806
for (Py_ssize_t i = 0; i < nargs; i++) {
@@ -806,36 +816,27 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type,
806816
if (attr == NULL) {
807817
goto fail;
808818
}
809-
if (PyList_Append(attrs, attr) < 0) {
810-
Py_DECREF(attr);
811-
goto fail;
812-
}
813-
Py_DECREF(attr);
819+
PyTuple_SET_ITEM(attrs, i, attr);
814820
}
815821
}
816822
Py_CLEAR(match_args);
817823
}
818824
// Finally, the keyword subpatterns:
819-
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwargs); i++) {
825+
for (Py_ssize_t i = 0; i < nkwargs; i++) {
820826
PyObject *name = PyTuple_GET_ITEM(kwargs, i);
821827
PyObject *attr = match_class_attr(tstate, subject, type, name, seen);
822828
if (attr == NULL) {
823829
goto fail;
824830
}
825-
if (PyList_Append(attrs, attr) < 0) {
826-
Py_DECREF(attr);
827-
goto fail;
828-
}
829-
Py_DECREF(attr);
831+
PyTuple_SET_ITEM(attrs, nargs + i, attr);
830832
}
831-
Py_SETREF(attrs, PyList_AsTuple(attrs));
832-
Py_DECREF(seen);
833+
Py_XDECREF(seen);
833834
return attrs;
834835
fail:
835836
// We really don't care whether an error was raised or not... that's our
836837
// caller's problem. All we know is that the match failed.
837838
Py_XDECREF(match_args);
838-
Py_DECREF(seen);
839+
Py_XDECREF(seen);
839840
Py_DECREF(attrs);
840841
return NULL;
841842
}

0 commit comments

Comments
 (0)