https://github.com/python/cpython/commit/d891b2bbd16c25995df853121d2f134d3e357cd1
commit: d891b2bbd16c25995df853121d2f134d3e357cd1
branch: main
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2026-02-06T09:43:05-05:00
summary:

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 <[email protected]>

* Apply suggestion from @vstinner

Co-authored-by: Victor Stinner <[email protected]>

* format

* Update Include/internal/pycore_function.h

Co-authored-by: Victor Stinner <[email protected]>

---------

Co-authored-by: Victor Stinner <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-01-29-16-57-11.gh-issue-139103.icXIEQ.rst
M Include/internal/pycore_function.h
M Include/internal/pycore_object.h
M Include/internal/pycore_typeobject.h
M Modules/_testinternalcapi/test_cases.c.h
M Objects/funcobject.c
M Objects/object.c
M Objects/typeobject.c
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/generated_cases.c.h
M Tools/ftscalingbench/ftscalingbench.py

diff --git a/Include/internal/pycore_function.h 
b/Include/internal/pycore_function.h
index e89f4b5c8a4ec1..522e03c6696993 100644
--- a/Include/internal/pycore_function.h
+++ b/Include/internal/pycore_function.h
@@ -47,6 +47,12 @@ static inline PyObject* _PyFunction_GET_BUILTINS(PyObject 
*func) {
 #define _PyFunction_GET_BUILTINS(func) 
_PyFunction_GET_BUILTINS(_PyObject_CAST(func))
 
 
+/* Get the callable wrapped by a staticmethod.
+   Returns a borrowed reference.
+   The caller must ensure 'sm' is a staticmethod object. */
+extern PyObject *_PyStaticMethod_GetFunc(PyObject *sm);
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index d14cee6af66103..8c241c7707d074 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -898,6 +898,10 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, 
PyObject *name, _PyStackRef
 PyAPI_FUNC(int) _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj,
                                        PyObject *name, _PyStackRef *method);
 
+// Like PyObject_GetAttr but returns a _PyStackRef. For types, this can
+// return a deferred reference to reduce reference count contention.
+PyAPI_FUNC(_PyStackRef) _PyObject_GetAttrStackRef(PyObject *obj, PyObject 
*name);
+
 // Cache the provided init method in the specialization cache of type if the
 // provided type version matches the current version of the type.
 //
diff --git a/Include/internal/pycore_typeobject.h 
b/Include/internal/pycore_typeobject.h
index abaa60890b55c8..dfd355d5012066 100644
--- a/Include/internal/pycore_typeobject.h
+++ b/Include/internal/pycore_typeobject.h
@@ -10,6 +10,7 @@ extern "C" {
 
 #include "pycore_interp_structs.h" // managed_static_type_state
 #include "pycore_moduleobject.h"  // PyModuleObject
+#include "pycore_structs.h"       // _PyStackRef
 
 
 /* state */
@@ -112,6 +113,8 @@ _PyType_IsReady(PyTypeObject *type)
 extern PyObject* _Py_type_getattro_impl(PyTypeObject *type, PyObject *name,
                                         int *suppress_missing_attribute);
 extern PyObject* _Py_type_getattro(PyObject *type, PyObject *name);
+extern _PyStackRef _Py_type_getattro_stackref(PyTypeObject *type, PyObject 
*name,
+                                              int *suppress_missing_attribute);
 
 extern PyObject* _Py_BaseObject_RichCompare(PyObject* self, PyObject* other, 
int op);
 
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-29-16-57-11.gh-issue-139103.icXIEQ.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-29-16-57-11.gh-issue-139103.icXIEQ.rst
new file mode 100644
index 00000000000000..de3391dfcea708
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-29-16-57-11.gh-issue-139103.icXIEQ.rst
@@ -0,0 +1,2 @@
+Improve scaling of :func:`~collections.namedtuple` instantiation in the
+free-threaded build.
diff --git a/Modules/_testinternalcapi/test_cases.c.h 
b/Modules/_testinternalcapi/test_cases.c.h
index c89c790988c52d..2a73a554eda2cc 100644
--- a/Modules/_testinternalcapi/test_cases.c.h
+++ b/Modules/_testinternalcapi/test_cases.c.h
@@ -7925,18 +7925,18 @@
                 }
                 else {
                     _PyFrame_SetStackPointer(frame, stack_pointer);
-                    PyObject *attr_o = 
PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+                    attr = 
_PyObject_GetAttrStackRef(PyStackRef_AsPyObjectBorrow(owner), name);
                     stack_pointer = _PyFrame_GetStackPointer(frame);
-                    stack_pointer += -1;
+                    stack_pointer[-1] = attr;
+                    stack_pointer += (oparg&1);
                     ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
                     _PyFrame_SetStackPointer(frame, stack_pointer);
                     PyStackRef_CLOSE(owner);
                     stack_pointer = _PyFrame_GetStackPointer(frame);
-                    if (attr_o == NULL) {
+                    if (PyStackRef_IsNull(attr)) {
                         JUMP_TO_LABEL(error);
                     }
-                    attr = PyStackRef_FromPyObjectSteal(attr_o);
-                    stack_pointer += 1;
+                    stack_pointer += -(oparg&1);
                 }
             }
             stack_pointer[-1] = attr;
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 2bf21fa045e3f1..8f4ff4e42392c2 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -7,6 +7,7 @@
 #include "pycore_long.h"          // _PyLong_GetOne()
 #include "pycore_modsupport.h"    // _PyArg_NoKeywords()
 #include "pycore_object.h"        // _PyObject_GC_UNTRACK()
+#include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount()
 #include "pycore_pyerrors.h"      // _PyErr_Occurred()
 #include "pycore_setobject.h"     // _PySet_NextEntry()
 #include "pycore_stats.h"
@@ -1760,6 +1761,7 @@ sm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     if (sm == NULL) {
         return NULL;
     }
+    _PyObject_SetDeferredRefcount((PyObject *)sm);
     if (sm_set_callable(sm, callable) < 0) {
         Py_DECREF(sm);
         return NULL;
@@ -1926,9 +1928,17 @@ PyStaticMethod_New(PyObject *callable)
     if (sm == NULL) {
         return NULL;
     }
+    _PyObject_SetDeferredRefcount((PyObject *)sm);
     if (sm_set_callable(sm, callable) < 0) {
         Py_DECREF(sm);
         return NULL;
     }
     return (PyObject *)sm;
 }
+
+PyObject *
+_PyStaticMethod_GetFunc(PyObject *self)
+{
+    staticmethod *sm = _PyStaticMethod_CAST(self);
+    return sm->sm_callable;
+}
diff --git a/Objects/object.c b/Objects/object.c
index 38717def24239f..a4f8ddf54b9484 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -31,6 +31,7 @@
 #include "pycore_tuple.h"         // _PyTuple_DebugMallocStats()
 #include "pycore_typeobject.h"    // _PyBufferWrapper_Type
 #include "pycore_typevarobject.h" // _PyTypeAlias_Type
+#include "pycore_stackref.h"      // PyStackRef_FromPyObjectSteal
 #include "pycore_unionobject.h"   // _PyUnion_Type
 
 
@@ -1334,6 +1335,54 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
     return result;
 }
 
+/* Like PyObject_GetAttr but returns a _PyStackRef.
+   For types (tp_getattro == _Py_type_getattro), this can return
+   a deferred reference to reduce reference count contention. */
+_PyStackRef
+_PyObject_GetAttrStackRef(PyObject *v, PyObject *name)
+{
+    PyTypeObject *tp = Py_TYPE(v);
+    if (!PyUnicode_Check(name)) {
+        PyErr_Format(PyExc_TypeError,
+                     "attribute name must be string, not '%.200s'",
+                     Py_TYPE(name)->tp_name);
+        return PyStackRef_NULL;
+    }
+
+    /* Fast path for types - can return deferred references */
+    if (tp->tp_getattro == _Py_type_getattro) {
+        _PyStackRef result = _Py_type_getattro_stackref((PyTypeObject *)v, 
name, NULL);
+        if (PyStackRef_IsNull(result)) {
+            _PyObject_SetAttributeErrorContext(v, name);
+        }
+        return result;
+    }
+
+    /* Fall back to regular PyObject_GetAttr and convert to stackref */
+    PyObject *result = NULL;
+    if (tp->tp_getattro != NULL) {
+        result = (*tp->tp_getattro)(v, name);
+    }
+    else if (tp->tp_getattr != NULL) {
+        const char *name_str = PyUnicode_AsUTF8(name);
+        if (name_str == NULL) {
+            return PyStackRef_NULL;
+        }
+        result = (*tp->tp_getattr)(v, (char *)name_str);
+    }
+    else {
+        PyErr_Format(PyExc_AttributeError,
+                    "'%.100s' object has no attribute '%U'",
+                    tp->tp_name, name);
+    }
+
+    if (result == NULL) {
+        _PyObject_SetAttributeErrorContext(v, name);
+        return PyStackRef_NULL;
+    }
+    return PyStackRef_FromPyObjectSteal(result);
+}
+
 int
 PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result)
 {
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index ac52fe4002dc69..ad26339c9c34df 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -6375,102 +6375,153 @@ _PyType_SetFlagsRecursive(PyTypeObject *self, 
unsigned long mask, unsigned long
 
    */
 PyObject *
-_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int * 
suppress_missing_attribute)
+_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int 
*suppress_missing_attribute)
+{
+    _PyStackRef ref = _Py_type_getattro_stackref(type, name, 
suppress_missing_attribute);
+    if (PyStackRef_IsNull(ref)) {
+        return NULL;
+    }
+    return PyStackRef_AsPyObjectSteal(ref);
+}
+
+/* This is similar to PyObject_GenericGetAttr(),
+   but uses _PyType_LookupRef() instead of just looking in type->tp_dict. */
+PyObject *
+_Py_type_getattro(PyObject *tp, PyObject *name)
+{
+    PyTypeObject *type = PyTypeObject_CAST(tp);
+    return _Py_type_getattro_impl(type, name, NULL);
+}
+
+/* Like _Py_type_getattro but returns a _PyStackRef.
+   This can return a deferred reference in the free-threaded build
+   when the attribute is found without going through a descriptor.
+
+   suppress_missing_attribute (optional):
+   * NULL: do not suppress the exception
+   * Non-zero pointer: suppress the PyExc_AttributeError and
+     set *suppress_missing_attribute to 1 to signal we are returning NULL while
+     having suppressed the exception (other exceptions are not suppressed)
+*/
+_PyStackRef
+_Py_type_getattro_stackref(PyTypeObject *type, PyObject *name,
+                           int *suppress_missing_attribute)
 {
     PyTypeObject *metatype = Py_TYPE(type);
-    PyObject *meta_attribute, *attribute;
-    descrgetfunc meta_get;
-    PyObject* res;
+    descrgetfunc meta_get = NULL;
 
     if (!PyUnicode_Check(name)) {
         PyErr_Format(PyExc_TypeError,
                      "attribute name must be string, not '%.200s'",
                      Py_TYPE(name)->tp_name);
-        return NULL;
+        return PyStackRef_NULL;
     }
 
     /* Initialize this type (we'll assume the metatype is initialized) */
     if (!_PyType_IsReady(type)) {
         if (PyType_Ready(type) < 0)
-            return NULL;
+            return PyStackRef_NULL;
     }
 
-    /* No readable descriptor found yet */
-    meta_get = NULL;
+    /* Set up GC-visible stack refs */
+    _PyCStackRef result_ref, meta_attribute_ref, attribute_ref;
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyThreadState_PushCStackRef(tstate, &result_ref);
+    _PyThreadState_PushCStackRef(tstate, &meta_attribute_ref);
+    _PyThreadState_PushCStackRef(tstate, &attribute_ref);
 
     /* Look for the attribute in the metatype */
-    meta_attribute = _PyType_LookupRef(metatype, name);
+    _PyType_LookupStackRefAndVersion(metatype, name, &meta_attribute_ref.ref);
 
-    if (meta_attribute != NULL) {
-        meta_get = Py_TYPE(meta_attribute)->tp_descr_get;
+    if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {
+        PyObject *meta_attr_obj = 
PyStackRef_AsPyObjectBorrow(meta_attribute_ref.ref);
+        meta_get = Py_TYPE(meta_attr_obj)->tp_descr_get;
 
-        if (meta_get != NULL && PyDescr_IsData(meta_attribute)) {
+        if (meta_get != NULL && PyDescr_IsData(meta_attr_obj)) {
             /* Data descriptors implement tp_descr_set to intercept
              * writes. Assume the attribute is not overridden in
              * type's tp_dict (and bases): call the descriptor now.
              */
-            res = meta_get(meta_attribute, (PyObject *)type,
-                           (PyObject *)metatype);
-            Py_DECREF(meta_attribute);
-            return res;
+            PyObject *res = meta_get(meta_attr_obj, (PyObject *)type,
+                                     (PyObject *)metatype);
+            if (res != NULL) {
+                result_ref.ref = PyStackRef_FromPyObjectSteal(res);
+            }
+            goto done;
         }
     }
 
     /* No data descriptor found on metatype. Look in tp_dict of this
      * type and its bases */
-    attribute = _PyType_LookupRef(type, name);
-    if (attribute != NULL) {
+    _PyType_LookupStackRefAndVersion(type, name, &attribute_ref.ref);
+    if (!PyStackRef_IsNull(attribute_ref.ref)) {
         /* Implement descriptor functionality, if any */
-        descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
+        PyObject *attr_obj = PyStackRef_AsPyObjectBorrow(attribute_ref.ref);
+        descrgetfunc local_get = Py_TYPE(attr_obj)->tp_descr_get;
 
-        Py_XDECREF(meta_attribute);
+        /* Release meta_attribute early since we found in local dict */
+        PyStackRef_CLEAR(meta_attribute_ref.ref);
 
         if (local_get != NULL) {
+            /* Special case staticmethod to avoid descriptor call overhead.
+             * staticmethod.__get__ just returns the wrapped callable. */
+            if (Py_TYPE(attr_obj) == &PyStaticMethod_Type) {
+                PyObject *callable = _PyStaticMethod_GetFunc(attr_obj);
+                if (callable) {
+                    result_ref.ref = PyStackRef_FromPyObjectNew(callable);
+                    goto done;
+                }
+            }
             /* NULL 2nd argument indicates the descriptor was
              * found on the target object itself (or a base)  */
-            res = local_get(attribute, (PyObject *)NULL,
-                            (PyObject *)type);
-            Py_DECREF(attribute);
-            return res;
+            PyObject *res = local_get(attr_obj, (PyObject *)NULL,
+                                      (PyObject *)type);
+            if (res != NULL) {
+                result_ref.ref = PyStackRef_FromPyObjectSteal(res);
+            }
+            goto done;
         }
 
-        return attribute;
+        /* No descriptor, return the attribute directly */
+        result_ref.ref = attribute_ref.ref;
+        attribute_ref.ref = PyStackRef_NULL;
+        goto done;
     }
 
     /* No attribute found in local __dict__ (or bases): use the
      * descriptor from the metatype, if any */
     if (meta_get != NULL) {
-        PyObject *res;
-        res = meta_get(meta_attribute, (PyObject *)type,
-                       (PyObject *)metatype);
-        Py_DECREF(meta_attribute);
-        return res;
+        PyObject *meta_attr_obj = 
PyStackRef_AsPyObjectBorrow(meta_attribute_ref.ref);
+        PyObject *res = meta_get(meta_attr_obj, (PyObject *)type,
+                                 (PyObject *)metatype);
+        if (res != NULL) {
+            result_ref.ref = PyStackRef_FromPyObjectSteal(res);
+        }
+        goto done;
     }
 
     /* If an ordinary attribute was found on the metatype, return it now */
-    if (meta_attribute != NULL) {
-        return meta_attribute;
+    if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {
+        result_ref.ref = meta_attribute_ref.ref;
+        meta_attribute_ref.ref = PyStackRef_NULL;
+        goto done;
     }
 
     /* Give up */
     if (suppress_missing_attribute == NULL) {
         PyErr_Format(PyExc_AttributeError,
-                        "type object '%.100s' has no attribute '%U'",
-                        type->tp_name, name);
-    } else {
+                     "type object '%.100s' has no attribute '%U'",
+                     type->tp_name, name);
+    }
+    else {
         // signal the caller we have not set an PyExc_AttributeError and gave 
up
         *suppress_missing_attribute = 1;
     }
-    return NULL;
-}
 
-/* This is similar to PyObject_GenericGetAttr(),
-   but uses _PyType_LookupRef() instead of just looking in type->tp_dict. */
-PyObject *
-_Py_type_getattro(PyObject *tp, PyObject *name)
-{
-    PyTypeObject *type = PyTypeObject_CAST(tp);
-    return _Py_type_getattro_impl(type, name, NULL);
+done:
+    _PyThreadState_PopCStackRef(tstate, &attribute_ref);
+    _PyThreadState_PopCStackRef(tstate, &meta_attribute_ref);
+    return _PyThreadState_PopCStackRefSteal(tstate, &result_ref);
 }
 
 // Called by type_setattro().  Updates both the type dict and
@@ -10937,15 +10988,19 @@ static PyObject *
 slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    PyObject *func, *result;
+    PyObject *result;
 
-    func = PyObject_GetAttr((PyObject *)type, &_Py_ID(__new__));
-    if (func == NULL) {
+    _PyCStackRef func_ref;
+    _PyThreadState_PushCStackRef(tstate, &func_ref);
+    func_ref.ref = _PyObject_GetAttrStackRef((PyObject *)type, 
&_Py_ID(__new__));
+    if (PyStackRef_IsNull(func_ref.ref)) {
+        _PyThreadState_PopCStackRef(tstate, &func_ref);
         return NULL;
     }
 
+    PyObject *func = PyStackRef_AsPyObjectBorrow(func_ref.ref);
     result = _PyObject_Call_Prepend(tstate, func, (PyObject *)type, args, 
kwds);
-    Py_DECREF(func);
+    _PyThreadState_PopCStackRef(tstate, &func_ref);
     return result;
 }
 
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index a014f56deb202e..818b4fbc3801c0 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -2392,10 +2392,9 @@ dummy_func(
             }
             else {
                 /* Classic, pushes one value. */
-                PyObject *attr_o = 
PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+                attr = 
_PyObject_GetAttrStackRef(PyStackRef_AsPyObjectBorrow(owner), name);
                 PyStackRef_CLOSE(owner);
-                ERROR_IF(attr_o == NULL);
-                attr = PyStackRef_FromPyObjectSteal(attr_o);
+                ERROR_IF(PyStackRef_IsNull(attr));
             }
         }
 
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 08c547c4a0a3b4..a98ec2200485d2 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -8703,19 +8703,19 @@
                 stack_pointer += 1;
                 ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
                 _PyFrame_SetStackPointer(frame, stack_pointer);
-                PyObject *attr_o = 
PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+                attr = 
_PyObject_GetAttrStackRef(PyStackRef_AsPyObjectBorrow(owner), name);
                 stack_pointer = _PyFrame_GetStackPointer(frame);
-                stack_pointer += -1;
+                stack_pointer[-1] = attr;
+                stack_pointer += (oparg&1);
                 ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
                 _PyFrame_SetStackPointer(frame, stack_pointer);
                 PyStackRef_CLOSE(owner);
                 stack_pointer = _PyFrame_GetStackPointer(frame);
-                if (attr_o == NULL) {
+                if (PyStackRef_IsNull(attr)) {
                     SET_CURRENT_CACHED_VALUES(0);
                     JUMP_TO_ERROR();
                 }
-                attr = PyStackRef_FromPyObjectSteal(attr_o);
-                stack_pointer += 1;
+                stack_pointer += -(oparg&1);
             }
             _tos_cache0 = PyStackRef_ZERO_BITS;
             _tos_cache1 = PyStackRef_ZERO_BITS;
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index be5dbfcc747935..fc1144a88d70cc 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -7924,18 +7924,18 @@
                 }
                 else {
                     _PyFrame_SetStackPointer(frame, stack_pointer);
-                    PyObject *attr_o = 
PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+                    attr = 
_PyObject_GetAttrStackRef(PyStackRef_AsPyObjectBorrow(owner), name);
                     stack_pointer = _PyFrame_GetStackPointer(frame);
-                    stack_pointer += -1;
+                    stack_pointer[-1] = attr;
+                    stack_pointer += (oparg&1);
                     ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
                     _PyFrame_SetStackPointer(frame, stack_pointer);
                     PyStackRef_CLOSE(owner);
                     stack_pointer = _PyFrame_GetStackPointer(frame);
-                    if (attr_o == NULL) {
+                    if (PyStackRef_IsNull(attr)) {
                         JUMP_TO_LABEL(error);
                     }
-                    attr = PyStackRef_FromPyObjectSteal(attr_o);
-                    stack_pointer += 1;
+                    stack_pointer += -(oparg&1);
                 }
             }
             stack_pointer[-1] = attr;
diff --git a/Tools/ftscalingbench/ftscalingbench.py 
b/Tools/ftscalingbench/ftscalingbench.py
index c2bd7c3880bc90..50d0e4c04fc319 100644
--- a/Tools/ftscalingbench/ftscalingbench.py
+++ b/Tools/ftscalingbench/ftscalingbench.py
@@ -28,8 +28,10 @@
 import sys
 import threading
 import time
+from collections import namedtuple
 from dataclasses import dataclass
 from operator import methodcaller
+from typing import NamedTuple
 
 # The iterations in individual benchmarks are scaled by this factor.
 WORK_SCALE = 100
@@ -215,6 +217,24 @@ def instantiate_dataclass():
     for _ in range(1000 * WORK_SCALE):
         obj = MyDataClass(x=1, y=2, z=3)
 
+MyNamedTuple = namedtuple("MyNamedTuple", ["x", "y", "z"])
+
+@register_benchmark
+def instantiate_namedtuple():
+    for _ in range(1000 * WORK_SCALE):
+        obj = MyNamedTuple(x=1, y=2, z=3)
+
+
+class MyTypingNamedTuple(NamedTuple):
+    x: int
+    y: int
+    z: int
+
+@register_benchmark
+def instantiate_typing_namedtuple():
+    for _ in range(1000 * WORK_SCALE):
+        obj = MyTypingNamedTuple(x=1, y=2, z=3)
+
 
 @register_benchmark
 def deepcopy():

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to