pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/commit/d891b2bbd16c25995df853121d2f134d3e357cd1

gh-139103: Improve namedtuple scaling in free-threaded build (gh-144332) · python/cpython@d891b2b · GitHub
Skip to content

Commit d891b2b

Browse files
colesburyvstinner
andauthored
gh-139103: Improve namedtuple scaling in free-threaded build (gh-144332)
Add `_Py_type_getattro_stackref`, a variant of type attribute lookup that returns `_PyStackRef` instead of `PyObject*`. This allows returning deferred references in the free-threaded build, reducing reference count contention when accessing type attributes. This significantly improves scaling of namedtuple instantiation across multiple threads. * Add blurb * Rename PyObject_GetAttrStackRef to _PyObject_GetAttrStackRef * Apply suggestion from @vstinner Co-authored-by: Victor Stinner <vstinner@python.org> * Apply suggestion from @vstinner Co-authored-by: Victor Stinner <vstinner@python.org> * format * Update Include/internal/pycore_function.h Co-authored-by: Victor Stinner <vstinner@python.org> --------- Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 638d22c commit d891b2b

12 files changed

Lines changed: 214 additions & 66 deletions

File tree

Include/internal/pycore_function.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ static inline PyObject* _PyFunction_GET_BUILTINS(PyObject *func) {
4747
#define _PyFunction_GET_BUILTINS(func) _PyFunction_GET_BUILTINS(_PyObject_CAST(func))
4848

4949

50+
/* Get the callable wrapped by a staticmethod.
51+
Returns a borrowed reference.
52+
The caller must ensure 'sm' is a staticmethod object. */
53+
extern PyObject *_PyStaticMethod_GetFunc(PyObject *sm);
54+
55+
5056
#ifdef __cplusplus
5157
}
5258
#endif

Include/internal/pycore_object.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,10 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
898898
PyAPI_FUNC(int) _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj,
899899
PyObject *name, _PyStackRef *method);
900900

901+
// Like PyObject_GetAttr but returns a _PyStackRef. For types, this can
902+
// return a deferred reference to reduce reference count contention.
903+
PyAPI_FUNC(_PyStackRef) _PyObject_GetAttrStackRef(PyObject *obj, PyObject *name);
904+
901905
// Cache the provided init method in the specialization cache of type if the
902906
// provided type version matches the current version of the type.
903907
//

Include/internal/pycore_typeobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ extern "C" {
1010

1111
#include "pycore_interp_structs.h" // managed_static_type_state
1212
#include "pycore_moduleobject.h" // PyModuleObject
13+
#include "pycore_structs.h" // _PyStackRef
1314

1415

1516
/* state */
@@ -112,6 +113,8 @@ _PyType_IsReady(PyTypeObject *type)
112113
extern PyObject* _Py_type_getattro_impl(PyTypeObject *type, PyObject *name,
113114
int *suppress_missing_attribute);
114115
extern PyObject* _Py_type_getattro(PyObject *type, PyObject *name);
116+
extern _PyStackRef _Py_type_getattro_stackref(PyTypeObject *type, PyObject *name,
117+
int *suppress_missing_attribute);
115118

116119
extern PyObject* _Py_BaseObject_RichCompare(PyObject* self, PyObject* other, int op);
117120

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve scaling of :func:`~collections.namedtuple` instantiation in the
2+
free-threaded build.

Modules/_testinternalcapi/test_cases.c.h

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/funcobject.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "pycore_long.h" // _PyLong_GetOne()
88
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
99
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
10+
#include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount()
1011
#include "pycore_pyerrors.h" // _PyErr_Occurred()
1112
#include "pycore_setobject.h" // _PySet_NextEntry()
1213
#include "pycore_stats.h"
@@ -1760,6 +1761,7 @@ sm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17601761
if (sm == NULL) {
17611762
return NULL;
17621763
}
1764+
_PyObject_SetDeferredRefcount((PyObject *)sm);
17631765
if (sm_set_callable(sm, callable) < 0) {
17641766
Py_DECREF(sm);
17651767
return NULL;
@@ -1926,9 +1928,17 @@ PyStaticMethod_New(PyObject *callable)
19261928
if (sm == NULL) {
19271929
return NULL;
19281930
}
1931+
_PyObject_SetDeferredRefcount((PyObject *)sm);
19291932
if (sm_set_callable(sm, callable) < 0) {
19301933
Py_DECREF(sm);
19311934
return NULL;
19321935
}
19331936
return (PyObject *)sm;
19341937
}
1938+
1939+
PyObject *
1940+
_PyStaticMethod_GetFunc(PyObject *self)
1941+
{
1942+
staticmethod *sm = _PyStaticMethod_CAST(self);
1943+
return sm->sm_callable;
1944+
}

Objects/object.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "pycore_tuple.h" // _PyTuple_DebugMallocStats()
3232
#include "pycore_typeobject.h" // _PyBufferWrapper_Type
3333
#include "pycore_typevarobject.h" // _PyTypeAlias_Type
34+
#include "pycore_stackref.h" // PyStackRef_FromPyObjectSteal
3435
#include "pycore_unionobject.h" // _PyUnion_Type
3536

3637

@@ -1334,6 +1335,54 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
13341335
return result;
13351336
}
13361337

1338+
/* Like PyObject_GetAttr but returns a _PyStackRef.
1339+
For types (tp_getattro == _Py_type_getattro), this can return
1340+
a deferred reference to reduce reference count contention. */
1341+
_PyStackRef
1342+
_PyObject_GetAttrStackRef(PyObject *v, PyObject *name)
1343+
{
1344+
PyTypeObject *tp = Py_TYPE(v);
1345+
if (!PyUnicode_Check(name)) {
1346+
PyErr_Format(PyExc_TypeError,
1347+
"attribute name must be string, not '%.200s'",
1348+
Py_TYPE(name)->tp_name);
1349+
return PyStackRef_NULL;
1350+
}
1351+
1352+
/* Fast path for types - can return deferred references */
1353+
if (tp->tp_getattro == _Py_type_getattro) {
1354+
_PyStackRef result = _Py_type_getattro_stackref((PyTypeObject *)v, name, NULL);
1355+
if (PyStackRef_IsNull(result)) {
1356+
_PyObject_SetAttributeErrorContext(v, name);
1357+
}
1358+
return result;
1359+
}
1360+
1361+
/* Fall back to regular PyObject_GetAttr and convert to stackref */
1362+
PyObject *result = NULL;
1363+
if (tp->tp_getattro != NULL) {
1364+
result = (*tp->tp_getattro)(v, name);
1365+
}
1366+
else if (tp->tp_getattr != NULL) {
1367+
const char *name_str = PyUnicode_AsUTF8(name);
1368+
if (name_str == NULL) {
1369+
return PyStackRef_NULL;
1370+
}
1371+
result = (*tp->tp_getattr)(v, (char *)name_str);
1372+
}
1373+
else {
1374+
PyErr_Format(PyExc_AttributeError,
1375+
"'%.100s' object has no attribute '%U'",
1376+
tp->tp_name, name);
1377+
}
1378+
1379+
if (result == NULL) {
1380+
_PyObject_SetAttributeErrorContext(v, name);
1381+
return PyStackRef_NULL;
1382+
}
1383+
return PyStackRef_FromPyObjectSteal(result);
1384+
}
1385+
13371386
int
13381387
PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result)
13391388
{

Objects/typeobject.c

Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6375,102 +6375,153 @@ _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long
63756375
63766376
*/
63776377
PyObject *
6378-
_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int * suppress_missing_attribute)
6378+
_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int *suppress_missing_attribute)
6379+
{
6380+
_PyStackRef ref = _Py_type_getattro_stackref(type, name, suppress_missing_attribute);
6381+
if (PyStackRef_IsNull(ref)) {
6382+
return NULL;
6383+
}
6384+
return PyStackRef_AsPyObjectSteal(ref);
6385+
}
6386+
6387+
/* This is similar to PyObject_GenericGetAttr(),
6388+
but uses _PyType_LookupRef() instead of just looking in type->tp_dict. */
6389+
PyObject *
6390+
_Py_type_getattro(PyObject *tp, PyObject *name)
6391+
{
6392+
PyTypeObject *type = PyTypeObject_CAST(tp);
6393+
return _Py_type_getattro_impl(type, name, NULL);
6394+
}
6395+
6396+
/* Like _Py_type_getattro but returns a _PyStackRef.
6397+
This can return a deferred reference in the free-threaded build
6398+
when the attribute is found without going through a descriptor.
6399+
6400+
suppress_missing_attribute (optional):
6401+
* NULL: do not suppress the exception
6402+
* Non-zero pointer: suppress the PyExc_AttributeError and
6403+
set *suppress_missing_attribute to 1 to signal we are returning NULL while
6404+
having suppressed the exception (other exceptions are not suppressed)
6405+
*/
6406+
_PyStackRef
6407+
_Py_type_getattro_stackref(PyTypeObject *type, PyObject *name,
6408+
int *suppress_missing_attribute)
63796409
{
63806410
PyTypeObject *metatype = Py_TYPE(type);
6381-
PyObject *meta_attribute, *attribute;
6382-
descrgetfunc meta_get;
6383-
PyObject* res;
6411+
descrgetfunc meta_get = NULL;
63846412

63856413
if (!PyUnicode_Check(name)) {
63866414
PyErr_Format(PyExc_TypeError,
63876415
"attribute name must be string, not '%.200s'",
63886416
Py_TYPE(name)->tp_name);
6389-
return NULL;
6417+
return PyStackRef_NULL;
63906418
}
63916419

63926420
/* Initialize this type (we'll assume the metatype is initialized) */
63936421
if (!_PyType_IsReady(type)) {
63946422
if (PyType_Ready(type) < 0)
6395-
return NULL;
6423+
return PyStackRef_NULL;
63966424
}
63976425

6398-
/* No readable descriptor found yet */
6399-
meta_get = NULL;
6426+
/* Set up GC-visible stack refs */
6427+
_PyCStackRef result_ref, meta_attribute_ref, attribute_ref;
6428+
PyThreadState *tstate = _PyThreadState_GET();
6429+
_PyThreadState_PushCStackRef(tstate, &result_ref);
6430+
_PyThreadState_PushCStackRef(tstate, &meta_attribute_ref);
6431+
_PyThreadState_PushCStackRef(tstate, &attribute_ref);
64006432

64016433
/* Look for the attribute in the metatype */
6402-
meta_attribute = _PyType_LookupRef(metatype, name);
6434+
_PyType_LookupStackRefAndVersion(metatype, name, &meta_attribute_ref.ref);
64036435

6404-
if (meta_attribute != NULL) {
6405-
meta_get = Py_TYPE(meta_attribute)->tp_descr_get;
6436+
if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {
6437+
PyObject *meta_attr_obj = PyStackRef_AsPyObjectBorrow(meta_attribute_ref.ref);
6438+
meta_get = Py_TYPE(meta_attr_obj)->tp_descr_get;
64066439

6407-
if (meta_get != NULL && PyDescr_IsData(meta_attribute)) {
6440+
if (meta_get != NULL && PyDescr_IsData(meta_attr_obj)) {
64086441
/* Data descriptors implement tp_descr_set to intercept
64096442
* writes. Assume the attribute is not overridden in
64106443
* type's tp_dict (and bases): call the descriptor now.
64116444
*/
6412-
res = meta_get(meta_attribute, (PyObject *)type,
6413-
(PyObject *)metatype);
6414-
Py_DECREF(meta_attribute);
6415-
return res;
6445+
PyObject *res = meta_get(meta_attr_obj, (PyObject *)type,
6446+
(PyObject *)metatype);
6447+
if (res != NULL) {
6448+
result_ref.ref = PyStackRef_FromPyObjectSteal(res);
6449+
}
6450+
goto done;
64166451
}
64176452
}
64186453

64196454
/* No data descriptor found on metatype. Look in tp_dict of this
64206455
* type and its bases */
6421-
attribute = _PyType_LookupRef(type, name);
6422-
if (attribute != NULL) {
6456+
_PyType_LookupStackRefAndVersion(type, name, &attribute_ref.ref);
6457+
if (!PyStackRef_IsNull(attribute_ref.ref)) {
64236458
/* Implement descriptor functionality, if any */
6424-
descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
6459+
PyObject *attr_obj = PyStackRef_AsPyObjectBorrow(attribute_ref.ref);
6460+
descrgetfunc local_get = Py_TYPE(attr_obj)->tp_descr_get;
64256461

6426-
Py_XDECREF(meta_attribute);
6462+
/* Release meta_attribute early since we found in local dict */
6463+
PyStackRef_CLEAR(meta_attribute_ref.ref);
64276464

64286465
if (local_get != NULL) {
6466+
/* Special case staticmethod to avoid descriptor call overhead.
6467+
* staticmethod.__get__ just returns the wrapped callable. */
6468+
if (Py_TYPE(attr_obj) == &PyStaticMethod_Type) {
6469+
PyObject *callable = _PyStaticMethod_GetFunc(attr_obj);
6470+
if (callable) {
6471+
result_ref.ref = PyStackRef_FromPyObjectNew(callable);
6472+
goto done;
6473+
}
6474+
}
64296475
/* NULL 2nd argument indicates the descriptor was
64306476
* found on the target object itself (or a base) */
6431-
res = local_get(attribute, (PyObject *)NULL,
6432-
(PyObject *)type);
6433-
Py_DECREF(attribute);
6434-
return res;
6477+
PyObject *res = local_get(attr_obj, (PyObject *)NULL,
6478+
(PyObject *)type);
6479+
if (res != NULL) {
6480+
result_ref.ref = PyStackRef_FromPyObjectSteal(res);
6481+
}
6482+
goto done;
64356483
}
64366484

6437-
return attribute;
6485+
/* No descriptor, return the attribute directly */
6486+
result_ref.ref = attribute_ref.ref;
6487+
attribute_ref.ref = PyStackRef_NULL;
6488+
goto done;
64386489
}
64396490

64406491
/* No attribute found in local __dict__ (or bases): use the
64416492
* descriptor from the metatype, if any */
64426493
if (meta_get != NULL) {
6443-
PyObject *res;
6444-
res = meta_get(meta_attribute, (PyObject *)type,
6445-
(PyObject *)metatype);
6446-
Py_DECREF(meta_attribute);
6447-
return res;
6494+
PyObject *meta_attr_obj = PyStackRef_AsPyObjectBorrow(meta_attribute_ref.ref);
6495+
PyObject *res = meta_get(meta_attr_obj, (PyObject *)type,
6496+
(PyObject *)metatype);
6497+
if (res != NULL) {
6498+
result_ref.ref = PyStackRef_FromPyObjectSteal(res);
6499+
}
6500+
goto done;
64486501
}
64496502

64506503
/* If an ordinary attribute was found on the metatype, return it now */
6451-
if (meta_attribute != NULL) {
6452-
return meta_attribute;
6504+
if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {
6505+
result_ref.ref = meta_attribute_ref.ref;
6506+
meta_attribute_ref.ref = PyStackRef_NULL;
6507+
goto done;
64536508
}
64546509

64556510
/* Give up */
64566511
if (suppress_missing_attribute == NULL) {
64576512
PyErr_Format(PyExc_AttributeError,
6458-
"type object '%.100s' has no attribute '%U'",
6459-
type->tp_name, name);
6460-
} else {
6513+
"type object '%.100s' has no attribute '%U'",
6514+
type->tp_name, name);
6515+
}
6516+
else {
64616517
// signal the caller we have not set an PyExc_AttributeError and gave up
64626518
*suppress_missing_attribute = 1;
64636519
}
6464-
return NULL;
6465-
}
64666520

6467-
/* This is similar to PyObject_GenericGetAttr(),
6468-
but uses _PyType_LookupRef() instead of just looking in type->tp_dict. */
6469-
PyObject *
6470-
_Py_type_getattro(PyObject *tp, PyObject *name)
6471-
{
6472-
PyTypeObject *type = PyTypeObject_CAST(tp);
6473-
return _Py_type_getattro_impl(type, name, NULL);
6521+
done:
6522+
_PyThreadState_PopCStackRef(tstate, &attribute_ref);
6523+
_PyThreadState_PopCStackRef(tstate, &meta_attribute_ref);
6524+
return _PyThreadState_PopCStackRefSteal(tstate, &result_ref);
64746525
}
64756526

64766527
// Called by type_setattro(). Updates both the type dict and
@@ -10937,15 +10988,19 @@ static PyObject *
1093710988
slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1093810989
{
1093910990
PyThreadState *tstate = _PyThreadState_GET();
10940-
PyObject *func, *result;
10991+
PyObject *result;
1094110992

10942-
func = PyObject_GetAttr((PyObject *)type, &_Py_ID(__new__));
10943-
if (func == NULL) {
10993+
_PyCStackRef func_ref;
10994+
_PyThreadState_PushCStackRef(tstate, &func_ref);
10995+
func_ref.ref = _PyObject_GetAttrStackRef((PyObject *)type, &_Py_ID(__new__));
10996+
if (PyStackRef_IsNull(func_ref.ref)) {
10997+
_PyThreadState_PopCStackRef(tstate, &func_ref);
1094410998
return NULL;
1094510999
}
1094611000

11001+
PyObject *func = PyStackRef_AsPyObjectBorrow(func_ref.ref);
1094711002
result = _PyObject_Call_Prepend(tstate, func, (PyObject *)type, args, kwds);
10948-
Py_DECREF(func);
11003+
_PyThreadState_PopCStackRef(tstate, &func_ref);
1094911004
return result;
1095011005
}
1095111006

Python/bytecodes.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2392,10 +2392,9 @@ dummy_func(
23922392
}
23932393
else {
23942394
/* Classic, pushes one value. */
2395-
PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
2395+
attr = _PyObject_GetAttrStackRef(PyStackRef_AsPyObjectBorrow(owner), name);
23962396
PyStackRef_CLOSE(owner);
2397-
ERROR_IF(attr_o == NULL);
2398-
attr = PyStackRef_FromPyObjectSteal(attr_o);
2397+
ERROR_IF(PyStackRef_IsNull(attr));
23992398
}
24002399
}
24012400

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy