Skip to content

Commit bdf6de8

Browse files
authored
gh-145685: Avoid contention on TYPE_LOCK in super() lookups (gh-145775)
1 parent cf7c67b commit bdf6de8

File tree

3 files changed

+32
-11
lines changed

3 files changed

+32
-11
lines changed

Include/internal/pycore_stackref.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,13 @@ _PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
770770
ref->ref = PyStackRef_NULL;
771771
}
772772

773+
static inline void
774+
_PyThreadState_PushCStackRefNew(PyThreadState *tstate, _PyCStackRef *ref, PyObject *obj)
775+
{
776+
_PyThreadState_PushCStackRef(tstate, ref);
777+
ref->ref = PyStackRef_FromPyObjectNew(obj);
778+
}
779+
773780
static inline void
774781
_PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
775782
{

Objects/typeobject.c

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12360,18 +12360,16 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1236012360
PyObject *mro, *res;
1236112361
Py_ssize_t i, n;
1236212362

12363-
BEGIN_TYPE_LOCK();
1236412363
mro = lookup_tp_mro(su_obj_type);
12365-
/* keep a strong reference to mro because su_obj_type->tp_mro can be
12366-
replaced during PyDict_GetItemRef(dict, name, &res) and because
12367-
another thread can modify it after we end the critical section
12368-
below */
12369-
Py_XINCREF(mro);
12370-
END_TYPE_LOCK();
12371-
1237212364
if (mro == NULL)
1237312365
return NULL;
1237412366

12367+
/* Keep a strong reference to mro because su_obj_type->tp_mro can be
12368+
replaced during PyDict_GetItemRef(dict, name, &res). */
12369+
PyThreadState *tstate = _PyThreadState_GET();
12370+
_PyCStackRef mro_ref;
12371+
_PyThreadState_PushCStackRefNew(tstate, &mro_ref, mro);
12372+
1237512373
assert(PyTuple_Check(mro));
1237612374
n = PyTuple_GET_SIZE(mro);
1237712375

@@ -12382,7 +12380,7 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1238212380
}
1238312381
i++; /* skip su->type (if any) */
1238412382
if (i >= n) {
12385-
Py_DECREF(mro);
12383+
_PyThreadState_PopCStackRef(tstate, &mro_ref);
1238612384
return NULL;
1238712385
}
1238812386

@@ -12393,13 +12391,13 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1239312391

1239412392
if (PyDict_GetItemRef(dict, name, &res) != 0) {
1239512393
// found or error
12396-
Py_DECREF(mro);
12394+
_PyThreadState_PopCStackRef(tstate, &mro_ref);
1239712395
return res;
1239812396
}
1239912397

1240012398
i++;
1240112399
} while (i < n);
12402-
Py_DECREF(mro);
12400+
_PyThreadState_PopCStackRef(tstate, &mro_ref);
1240312401
return NULL;
1240412402
}
1240512403

Tools/ftscalingbench/ftscalingbench.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,22 @@ def instantiate_typing_namedtuple():
241241
for _ in range(1000 * WORK_SCALE):
242242
obj = MyTypingNamedTuple(x=1, y=2, z=3)
243243

244+
@register_benchmark
245+
def super_call():
246+
# TODO: super() on the same class from multiple threads still doesn't
247+
# scale well, so use a class per-thread here for now.
248+
class Base:
249+
def method(self):
250+
return 1
251+
252+
class Derived(Base):
253+
def method(self):
254+
return super().method()
255+
256+
obj = Derived()
257+
for _ in range(1000 * WORK_SCALE):
258+
obj.method()
259+
244260

245261
@register_benchmark
246262
def deepcopy():

0 commit comments

Comments
 (0)