Skip to content

Commit 42d8d7e

Browse files
committed
gh-139103: Use borrowed references for positional args in _PyStack_UnpackDict
The positional arguments passed to _PyStack_UnpackDict are already kept alive by the caller, so we can avoid the extra reference count operations by using borrowed references instead of creating new ones. This reduces reference count contention in the free-threaded build when calling functions with keyword arguments. In particular, this avoids contention on the type argument to `__new__` when instantiating namedtuples with keyword arguments.
1 parent 29acc08 commit 42d8d7e

File tree

2 files changed

+18
-8
lines changed

2 files changed

+18
-8
lines changed

Objects/call.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,10 @@ _PyStack_AsDict(PyObject *const *values, PyObject *kwnames)
935935
936936
The newly allocated argument vector supports PY_VECTORCALL_ARGUMENTS_OFFSET.
937937
938+
The positional arguments are borrowed references from the input array
939+
(which must be kept alive by the caller). The keyword argument values
940+
are new references.
941+
938942
When done, you must call _PyStack_UnpackDict_Free(stack, nargs, kwnames) */
939943
PyObject *const *
940944
_PyStack_UnpackDict(PyThreadState *tstate,
@@ -970,9 +974,9 @@ _PyStack_UnpackDict(PyThreadState *tstate,
970974

971975
stack++; /* For PY_VECTORCALL_ARGUMENTS_OFFSET */
972976

973-
/* Copy positional arguments */
977+
/* Copy positional arguments (borrowed references) */
974978
for (Py_ssize_t i = 0; i < nargs; i++) {
975-
stack[i] = Py_NewRef(args[i]);
979+
stack[i] = args[i];
976980
}
977981

978982
PyObject **kwstack = stack + nargs;
@@ -1009,9 +1013,10 @@ void
10091013
_PyStack_UnpackDict_Free(PyObject *const *stack, Py_ssize_t nargs,
10101014
PyObject *kwnames)
10111015
{
1012-
Py_ssize_t n = PyTuple_GET_SIZE(kwnames) + nargs;
1013-
for (Py_ssize_t i = 0; i < n; i++) {
1014-
Py_DECREF(stack[i]);
1016+
/* Only decref kwargs values, positional args are borrowed */
1017+
Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwnames);
1018+
for (Py_ssize_t i = 0; i < nkwargs; i++) {
1019+
Py_DECREF(stack[nargs + i]);
10151020
}
10161021
_PyStack_UnpackDict_FreeNoDecRef(stack, kwnames);
10171022
}

Python/ceval.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,11 +2000,16 @@ _PyEvalFramePushAndInit_Ex(PyThreadState *tstate, _PyStackRef func,
20002000
PyStackRef_CLOSE(func);
20012001
goto error;
20022002
}
2003-
size_t total_args = nargs + PyDict_GET_SIZE(kwargs);
2003+
size_t nkwargs = PyDict_GET_SIZE(kwargs);
20042004
assert(sizeof(PyObject *) == sizeof(_PyStackRef));
20052005
newargs = (_PyStackRef *)object_array;
2006-
for (size_t i = 0; i < total_args; i++) {
2007-
newargs[i] = PyStackRef_FromPyObjectSteal(object_array[i]);
2006+
/* Positional args are borrowed from callargs tuple, need new reference */
2007+
for (Py_ssize_t i = 0; i < nargs; i++) {
2008+
newargs[i] = PyStackRef_FromPyObjectNew(object_array[i]);
2009+
}
2010+
/* Keyword args are owned by _PyStack_UnpackDict, steal them */
2011+
for (size_t i = 0; i < nkwargs; i++) {
2012+
newargs[nargs + i] = PyStackRef_FromPyObjectSteal(object_array[nargs + i]);
20082013
}
20092014
}
20102015
else {

0 commit comments

Comments
 (0)