]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-127119: Faster check for small ints in long_dealloc (GH-127620)
authorPieter Eendebak <pieter.eendebak@gmail.com>
Wed, 29 Jan 2025 15:22:18 +0000 (16:22 +0100)
committerGitHub <noreply@github.com>
Wed, 29 Jan 2025 15:22:18 +0000 (15:22 +0000)
Include/cpython/longintrepr.h
Include/internal/pycore_long.h
Misc/NEWS.d/next/Core_and_Builtins/2024-12-04-22-14-40.gh-issue-127119._hpyFE.rst [new file with mode: 0644]
Modules/_testcapi/immortal.c
Objects/longobject.c
Tools/gdb/libpython.py

index 4dd82600d562ee337e409abd105b91cf0578c931..4b6f97a5e475d65497a0bc0b1d51425b612ae5c2 100644 (file)
@@ -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,
index 8bead00e70640c0aa1a6dd474ba3c7d8f61fce96..c52eb77692dd6a2a4a06f4e36ee6bc63d78b8115 100644 (file)
@@ -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 (file)
index 0000000..f021bd4
--- /dev/null
@@ -0,0 +1 @@
+Slightly optimize the :class:`int` deallocator.
index 5bdae2e99d537525ac035473ba486bf7e0edb8ca..0663c3781d426a23639b4ee4cc1071e8ba05ee6e 100644 (file)
@@ -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;
 }
index 43be1ab056e0fe98b6f5efed55225bf03d327d73..370328dcfe8c9aa41aa5c274dd183629fca68c82 100644 (file)
@@ -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];
     }
index e0d92e21dc42b3a032365355316c9f8daa7e1380..27aa6b0cc266d3950ccf154787aa404d12a19729 100755 (executable)
@@ -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 @@ class PyLongObjectPtr(PyObjectPtr):
                 - 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