https://github.com/python/cpython/commit/a29221675e7367608961c3484701ab2671ec6f3c
commit: a29221675e7367608961c3484701ab2671ec6f3c
branch: main
author: Pieter Eendebak <[email protected]>
committer: markshannon <[email protected]>
date: 2025-01-29T15:22:18Z
summary:
gh-127119: Faster check for small ints in long_dealloc (GH-127620)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst
M Include/cpython/longintrepr.h
M Include/internal/pycore_long.h
M Modules/_testcapi/immortal.c
M Objects/longobject.c
M Tools/gdb/libpython.py
diff --git a/Include/cpython/longintrepr.h b/Include/cpython/longintrepr.h
index 4dd82600d562ee..4b6f97a5e475d6 100644
--- a/Include/cpython/longintrepr.h
+++ b/Include/cpython/longintrepr.h
@@ -76,8 +76,8 @@ typedef long stwodigits; /* signed variant of twodigits */
- 1: Zero
- 2: Negative
- The third lowest bit of lv_tag is reserved for an immortality flag, but is
- not currently used.
+ The third lowest bit of lv_tag is
+ set to 1 for the small ints.
In a normalized number, ob_digit[ndigits-1] (the most significant
digit) is never zero. Also, in all cases, for all valid i,
diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h
index 8bead00e70640c..c52eb77692dd6a 100644
--- a/Include/internal/pycore_long.h
+++ b/Include/internal/pycore_long.h
@@ -159,13 +159,14 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void
*);
/* Long value tag bits:
* 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1.
- * 2: Reserved for immortality bit
+ * 2: Set to 1 for the small ints
* 3+ Unsigned digit count
*/
#define SIGN_MASK 3
#define SIGN_ZERO 1
#define SIGN_NEGATIVE 2
#define NON_SIZE_BITS 3
+#define IMMORTALITY_BIT_MASK (1 << 2)
/* The functions _PyLong_IsCompact and _PyLong_CompactValue are defined
* in Include/cpython/longobject.h, since they need to be inline.
@@ -196,7 +197,7 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void
*);
static inline int
_PyLong_IsNonNegativeCompact(const PyLongObject* op) {
assert(PyLong_Check(op));
- return op->long_value.lv_tag <= (1 << NON_SIZE_BITS);
+ return ((op->long_value.lv_tag & ~IMMORTALITY_BIT_MASK) <= (1 <<
NON_SIZE_BITS));
}
@@ -298,7 +299,7 @@ _PyLong_FlipSign(PyLongObject *op) {
.long_value = { \
.lv_tag = TAG_FROM_SIGN_AND_SIZE( \
(val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \
- (val) == 0 ? 0 : 1), \
+ (val) == 0 ? 0 : 1) | IMMORTALITY_BIT_MASK, \
{ ((val) >= 0 ? (val) : -(val)) }, \
} \
}
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst
new file mode 100644
index 00000000000000..f021bd490f488c
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst
@@ -0,0 +1 @@
+Slightly optimize the :class:`int` deallocator.
diff --git a/Modules/_testcapi/immortal.c b/Modules/_testcapi/immortal.c
index 5bdae2e99d5375..0663c3781d426a 100644
--- a/Modules/_testcapi/immortal.c
+++ b/Modules/_testcapi/immortal.c
@@ -1,5 +1,8 @@
#include "parts.h"
+#define Py_BUILD_CORE
+#include "internal/pycore_long.h" // IMMORTALITY_BIT_MASK
+
int verify_immortality(PyObject *object)
{
assert(_Py_IsImmortal(object));
@@ -26,7 +29,17 @@ static PyObject *
test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
{
for (int i = -5; i <= 256; i++) {
- assert(verify_immortality(PyLong_FromLong(i)));
+ PyObject *obj = PyLong_FromLong(i);
+ assert(verify_immortality(obj));
+ int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag &
IMMORTALITY_BIT_MASK;
+ assert(has_int_immortal_bit);
+ }
+ for (int i = 257; i <= 260; i++) {
+ PyObject *obj = PyLong_FromLong(i);
+ assert(obj);
+ int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag &
IMMORTALITY_BIT_MASK;
+ assert(!has_int_immortal_bit);
+ Py_DECREF(obj);
}
Py_RETURN_NONE;
}
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 43be1ab056e0fe..370328dcfe8c9a 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -3651,32 +3651,25 @@ long_richcompare(PyObject *self, PyObject *other, int
op)
}
static inline int
-compact_int_is_small(PyObject *self)
+/// Return 1 if the object is one of the immortal small ints
+_long_is_small_int(PyObject *op)
{
- PyLongObject *pylong = (PyLongObject *)self;
- assert(_PyLong_IsCompact(pylong));
- stwodigits ival = medium_value(pylong);
- if (IS_SMALL_INT(ival)) {
- PyLongObject *small_pylong = (PyLongObject
*)get_small_int((sdigit)ival);
- if (pylong == small_pylong) {
- return 1;
- }
- }
- return 0;
+ PyLongObject *long_object = (PyLongObject *)op;
+ int is_small_int = (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK)
!= 0;
+ assert((!is_small_int) || PyLong_CheckExact(op));
+ return is_small_int;
}
void
_PyLong_ExactDealloc(PyObject *self)
{
assert(PyLong_CheckExact(self));
+ if (_long_is_small_int(self)) {
+ // See PEP 683, section Accidental De-Immortalizing for details
+ _Py_SetImmortal(self);
+ return;
+ }
if (_PyLong_IsCompact((PyLongObject *)self)) {
- #ifndef Py_GIL_DISABLED
- if (compact_int_is_small(self)) {
- // See PEP 683, section Accidental De-Immortalizing for details
- _Py_SetImmortal(self);
- return;
- }
- #endif
_Py_FREELIST_FREE(ints, self, PyObject_Free);
return;
}
@@ -3686,24 +3679,20 @@ _PyLong_ExactDealloc(PyObject *self)
static void
long_dealloc(PyObject *self)
{
- assert(self);
- if (_PyLong_IsCompact((PyLongObject *)self)) {
- if (compact_int_is_small(self)) {
- /* This should never get called, but we also don't want to SEGV if
- * we accidentally decref small Ints out of existence. Instead,
- * since small Ints are immortal, re-set the reference count.
- *
- * See PEP 683, section Accidental De-Immortalizing for details
- */
- _Py_SetImmortal(self);
- return;
- }
- if (PyLong_CheckExact(self)) {
- _Py_FREELIST_FREE(ints, self, PyObject_Free);
- return;
- }
+ if (_long_is_small_int(self)) {
+ /* This should never get called, but we also don't want to SEGV if
+ * we accidentally decref small Ints out of existence. Instead,
+ * since small Ints are immortal, re-set the reference count.
+ *
+ * See PEP 683, section Accidental De-Immortalizing for details
+ */
+ _Py_SetImmortal(self);
+ return;
+ }
+ if (PyLong_CheckExact(self) && _PyLong_IsCompact((PyLongObject *)self)) {
+ _Py_FREELIST_FREE(ints, self, PyObject_Free);
+ return;
}
-
Py_TYPE(self)->tp_free(self);
}
@@ -6065,7 +6054,7 @@ long_subtype_new(PyTypeObject *type, PyObject *x,
PyObject *obase)
return NULL;
}
assert(PyLong_Check(newobj));
- newobj->long_value.lv_tag = tmp->long_value.lv_tag;
+ newobj->long_value.lv_tag = tmp->long_value.lv_tag & ~IMMORTALITY_BIT_MASK;
for (i = 0; i < n; i++) {
newobj->long_value.ob_digit[i] = tmp->long_value.ob_digit[i];
}
diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py
index e0d92e21dc42b3..27aa6b0cc266d3 100755
--- a/Tools/gdb/libpython.py
+++ b/Tools/gdb/libpython.py
@@ -890,7 +890,7 @@ class PyLongObjectPtr(PyObjectPtr):
def proxyval(self, visited):
'''
- Python's Include/longinterpr.h has this declaration:
+ Python's Include/cpython/longinterpr.h has this declaration:
typedef struct _PyLongValue {
uintptr_t lv_tag; /* Number of digits, sign and flags */
@@ -909,8 +909,7 @@ def proxyval(self, visited):
- 0: Positive
- 1: Zero
- 2: Negative
- The third lowest bit of lv_tag is reserved for an immortality
flag, but is
- not currently used.
+ The third lowest bit of lv_tag is set to 1 for the small ints and
0 otherwise.
where SHIFT can be either:
#define PyLong_SHIFT 30
_______________________________________________
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]