Skip to content

Commit 3c05251

Browse files
gh-134637: Fix performance regression in calling ctypes function pointer in free threading. (#134702)
Fix performance regression in calling `ctypes` function pointer in `free threading`.
1 parent 56743af commit 3c05251

File tree

2 files changed

+91
-52
lines changed

2 files changed

+91
-52
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix performance regression in calling a :mod:`ctypes` function pointer in :term:`free threading`.

Modules/_ctypes/_ctypes.c

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3610,6 +3610,45 @@ generic_pycdata_new(ctypes_state *st,
36103610
PyCFuncPtr_Type
36113611
*/
36123612

3613+
static inline void
3614+
atomic_xsetref(PyObject **field, PyObject *value)
3615+
{
3616+
#ifdef Py_GIL_DISABLED
3617+
PyObject *old = *field;
3618+
_Py_atomic_store_ptr(field, value);
3619+
Py_XDECREF(old);
3620+
#else
3621+
Py_XSETREF(*field, value);
3622+
#endif
3623+
}
3624+
/*
3625+
This function atomically loads the reference from *field, and
3626+
tries to get a new reference to it. If the incref fails,
3627+
it acquires critical section of obj and returns a new reference to the *field.
3628+
In the general case, this avoids contention on acquiring the critical section.
3629+
*/
3630+
static inline PyObject *
3631+
atomic_xgetref(PyObject *obj, PyObject **field)
3632+
{
3633+
#ifdef Py_GIL_DISABLED
3634+
PyObject *value = _Py_atomic_load_ptr(field);
3635+
if (value == NULL) {
3636+
return NULL;
3637+
}
3638+
if (_Py_TryIncrefCompare(field, value)) {
3639+
return value;
3640+
}
3641+
Py_BEGIN_CRITICAL_SECTION(obj);
3642+
value = Py_XNewRef(*field);
3643+
Py_END_CRITICAL_SECTION();
3644+
return value;
3645+
#else
3646+
return Py_XNewRef(*field);
3647+
#endif
3648+
}
3649+
3650+
3651+
36133652
/*[clinic input]
36143653
@critical_section
36153654
@setter
@@ -3626,7 +3665,7 @@ _ctypes_CFuncPtr_errcheck_set_impl(PyCFuncPtrObject *self, PyObject *value)
36263665
return -1;
36273666
}
36283667
Py_XINCREF(value);
3629-
Py_XSETREF(self->errcheck, value);
3668+
atomic_xsetref(&self->errcheck, value);
36303669
return 0;
36313670
}
36323671

@@ -3658,12 +3697,10 @@ static int
36583697
_ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
36593698
/*[clinic end generated code: output=0be0a086abbabf18 input=683c3bef4562ccc6]*/
36603699
{
3661-
PyObject *checker, *oldchecker;
3700+
PyObject *checker;
36623701
if (value == NULL) {
3663-
oldchecker = self->checker;
3664-
self->checker = NULL;
3665-
Py_CLEAR(self->restype);
3666-
Py_XDECREF(oldchecker);
3702+
atomic_xsetref(&self->restype, NULL);
3703+
atomic_xsetref(&self->checker, NULL);
36673704
return 0;
36683705
}
36693706
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
@@ -3679,11 +3716,9 @@ _ctypes_CFuncPtr_restype_set_impl(PyCFuncPtrObject *self, PyObject *value)
36793716
if (PyObject_GetOptionalAttr(value, &_Py_ID(_check_retval_), &checker) < 0) {
36803717
return -1;
36813718
}
3682-
oldchecker = self->checker;
3683-
self->checker = checker;
36843719
Py_INCREF(value);
3685-
Py_XSETREF(self->restype, value);
3686-
Py_XDECREF(oldchecker);
3720+
atomic_xsetref(&self->checker, checker);
3721+
atomic_xsetref(&self->restype, value);
36873722
return 0;
36883723
}
36893724

@@ -3728,16 +3763,16 @@ _ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value)
37283763
PyObject *converters;
37293764

37303765
if (value == NULL || value == Py_None) {
3731-
Py_CLEAR(self->converters);
3732-
Py_CLEAR(self->argtypes);
3766+
atomic_xsetref(&self->argtypes, NULL);
3767+
atomic_xsetref(&self->converters, NULL);
37333768
} else {
37343769
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
37353770
converters = converters_from_argtypes(st, value);
37363771
if (!converters)
37373772
return -1;
3738-
Py_XSETREF(self->converters, converters);
3773+
atomic_xsetref(&self->converters, converters);
37393774
Py_INCREF(value);
3740-
Py_XSETREF(self->argtypes, value);
3775+
atomic_xsetref(&self->argtypes, value);
37413776
}
37423777
return 0;
37433778
}
@@ -4533,16 +4568,11 @@ _build_result(PyObject *result, PyObject *callargs,
45334568
}
45344569

45354570
static PyObject *
4536-
PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
4571+
PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
45374572
{
4538-
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
4539-
PyObject *restype;
4540-
PyObject *converters;
4541-
PyObject *checker;
4542-
PyObject *argtypes;
4543-
PyObject *result;
4544-
PyObject *callargs;
4545-
PyObject *errcheck;
4573+
PyObject *result = NULL;
4574+
PyObject *callargs = NULL;
4575+
PyObject *ret = NULL;
45464576
#ifdef MS_WIN32
45474577
IUnknown *piunk = NULL;
45484578
#endif
@@ -4560,13 +4590,24 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
45604590
}
45614591
assert(info); /* Cannot be NULL for PyCFuncPtrObject instances */
45624592

4563-
restype = self->restype ? self->restype : info->restype;
4564-
converters = self->converters ? self->converters : info->converters;
4565-
checker = self->checker ? self->checker : info->checker;
4566-
argtypes = self->argtypes ? self->argtypes : info->argtypes;
4567-
/* later, we probably want to have an errcheck field in stginfo */
4568-
errcheck = self->errcheck /* ? self->errcheck : info->errcheck */;
4569-
4593+
PyObject *restype = atomic_xgetref(op, &self->restype);
4594+
if (restype == NULL) {
4595+
restype = Py_XNewRef(info->restype);
4596+
}
4597+
PyObject *converters = atomic_xgetref(op, &self->converters);
4598+
if (converters == NULL) {
4599+
converters = Py_XNewRef(info->converters);
4600+
}
4601+
PyObject *checker = atomic_xgetref(op, &self->checker);
4602+
if (checker == NULL) {
4603+
checker = Py_XNewRef(info->checker);
4604+
}
4605+
PyObject *argtypes = atomic_xgetref(op, &self->argtypes);
4606+
if (argtypes == NULL) {
4607+
argtypes = Py_XNewRef(info->argtypes);
4608+
}
4609+
/* later, we probably want to have an errcheck field in stginfo */
4610+
PyObject *errcheck = atomic_xgetref(op, &self->errcheck);
45704611

45714612
pProc = *(void **)self->b_ptr;
45724613
#ifdef MS_WIN32
@@ -4577,34 +4618,35 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
45774618
if (!this) {
45784619
PyErr_SetString(PyExc_ValueError,
45794620
"native com method call without 'this' parameter");
4580-
return NULL;
4621+
goto finally;
45814622
}
45824623
if (!CDataObject_Check(st, this)) {
45834624
PyErr_SetString(PyExc_TypeError,
45844625
"Expected a COM this pointer as first argument");
4585-
return NULL;
4626+
goto finally;
45864627
}
45874628
/* there should be more checks? No, in Python */
45884629
/* First arg is a pointer to an interface instance */
45894630
if (!this->b_ptr || *(void **)this->b_ptr == NULL) {
45904631
PyErr_SetString(PyExc_ValueError,
45914632
"NULL COM pointer access");
4592-
return NULL;
4633+
goto finally;
45934634
}
45944635
piunk = *(IUnknown **)this->b_ptr;
45954636
if (NULL == piunk->lpVtbl) {
45964637
PyErr_SetString(PyExc_ValueError,
45974638
"COM method call without VTable");
4598-
return NULL;
4639+
goto finally;
45994640
}
46004641
pProc = ((void **)piunk->lpVtbl)[self->index - 0x1000];
46014642
}
46024643
#endif
46034644
callargs = _build_callargs(st, self, argtypes,
46044645
inargs, kwds,
46054646
&outmask, &inoutmask, &numretvals);
4606-
if (callargs == NULL)
4607-
return NULL;
4647+
if (callargs == NULL) {
4648+
goto finally;
4649+
}
46084650

46094651
if (converters) {
46104652
int required = Py_SAFE_DOWNCAST(PyTuple_GET_SIZE(converters),
@@ -4623,7 +4665,7 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
46234665
required,
46244666
required == 1 ? "" : "s",
46254667
actual);
4626-
return NULL;
4668+
goto finally;
46274669
}
46284670
} else if (required != actual) {
46294671
Py_DECREF(callargs);
@@ -4632,7 +4674,7 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
46324674
required,
46334675
required == 1 ? "" : "s",
46344676
actual);
4635-
return NULL;
4677+
goto finally;
46364678
}
46374679
}
46384680

@@ -4663,23 +4705,19 @@ PyCFuncPtr_call_lock_held(PyObject *op, PyObject *inargs, PyObject *kwds)
46634705
if (v == NULL || v != callargs) {
46644706
Py_DECREF(result);
46654707
Py_DECREF(callargs);
4666-
return v;
4708+
ret = v;
4709+
goto finally;
46674710
}
46684711
Py_DECREF(v);
46694712
}
4670-
4671-
return _build_result(result, callargs,
4672-
outmask, inoutmask, numretvals);
4673-
}
4674-
4675-
static PyObject *
4676-
PyCFuncPtr_call(PyObject *op, PyObject *inargs, PyObject *kwds)
4677-
{
4678-
PyObject *result;
4679-
Py_BEGIN_CRITICAL_SECTION(op);
4680-
result = PyCFuncPtr_call_lock_held(op, inargs, kwds);
4681-
Py_END_CRITICAL_SECTION();
4682-
return result;
4713+
ret = _build_result(result, callargs, outmask, inoutmask, numretvals);
4714+
finally:
4715+
Py_XDECREF(restype);
4716+
Py_XDECREF(converters);
4717+
Py_XDECREF(checker);
4718+
Py_XDECREF(argtypes);
4719+
Py_XDECREF(errcheck);
4720+
return ret;
46834721
}
46844722

46854723
static int

0 commit comments

Comments
 (0)