gh-127119: Faster check for small ints in long_dealloc (GH-127620)

This commit is contained in:
Pieter Eendebak 2025-01-29 16:22:18 +01:00 committed by GitHub
parent 3a974e39d5
commit a29221675e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 48 additions and 45 deletions

View file

@ -76,8 +76,8 @@ typedef long stwodigits; /* signed variant of twodigits */
- 1: Zero - 1: Zero
- 2: Negative - 2: Negative
The third lowest bit of lv_tag is reserved for an immortality flag, but is The third lowest bit of lv_tag is
not currently used. set to 1 for the small ints.
In a normalized number, ob_digit[ndigits-1] (the most significant In a normalized number, ob_digit[ndigits-1] (the most significant
digit) is never zero. Also, in all cases, for all valid i, digit) is never zero. Also, in all cases, for all valid i,

View file

@ -159,13 +159,14 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void *);
/* Long value tag bits: /* Long value tag bits:
* 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1. * 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 * 3+ Unsigned digit count
*/ */
#define SIGN_MASK 3 #define SIGN_MASK 3
#define SIGN_ZERO 1 #define SIGN_ZERO 1
#define SIGN_NEGATIVE 2 #define SIGN_NEGATIVE 2
#define NON_SIZE_BITS 3 #define NON_SIZE_BITS 3
#define IMMORTALITY_BIT_MASK (1 << 2)
/* The functions _PyLong_IsCompact and _PyLong_CompactValue are defined /* The functions _PyLong_IsCompact and _PyLong_CompactValue are defined
* in Include/cpython/longobject.h, since they need to be inline. * 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 static inline int
_PyLong_IsNonNegativeCompact(const PyLongObject* op) { _PyLong_IsNonNegativeCompact(const PyLongObject* op) {
assert(PyLong_Check(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 = { \ .long_value = { \
.lv_tag = TAG_FROM_SIGN_AND_SIZE( \ .lv_tag = TAG_FROM_SIGN_AND_SIZE( \
(val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \ (val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \
(val) == 0 ? 0 : 1), \ (val) == 0 ? 0 : 1) | IMMORTALITY_BIT_MASK, \
{ ((val) >= 0 ? (val) : -(val)) }, \ { ((val) >= 0 ? (val) : -(val)) }, \
} \ } \
} }

View file

@ -0,0 +1 @@
Slightly optimize the :class:`int` deallocator.

View file

@ -1,5 +1,8 @@
#include "parts.h" #include "parts.h"
#define Py_BUILD_CORE
#include "internal/pycore_long.h" // IMMORTALITY_BIT_MASK
int verify_immortality(PyObject *object) int verify_immortality(PyObject *object)
{ {
assert(_Py_IsImmortal(object)); assert(_Py_IsImmortal(object));
@ -26,7 +29,17 @@ static PyObject *
test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored)) test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
{ {
for (int i = -5; i <= 256; i++) { 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; Py_RETURN_NONE;
} }

View file

@ -3651,32 +3651,25 @@ long_richcompare(PyObject *self, PyObject *other, int op)
} }
static inline int 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; PyLongObject *long_object = (PyLongObject *)op;
assert(_PyLong_IsCompact(pylong)); int is_small_int = (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
stwodigits ival = medium_value(pylong); assert((!is_small_int) || PyLong_CheckExact(op));
if (IS_SMALL_INT(ival)) { return is_small_int;
PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival);
if (pylong == small_pylong) {
return 1;
}
}
return 0;
} }
void void
_PyLong_ExactDealloc(PyObject *self) _PyLong_ExactDealloc(PyObject *self)
{ {
assert(PyLong_CheckExact(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)) { 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); _Py_FREELIST_FREE(ints, self, PyObject_Free);
return; return;
} }
@ -3686,24 +3679,20 @@ _PyLong_ExactDealloc(PyObject *self)
static void static void
long_dealloc(PyObject *self) long_dealloc(PyObject *self)
{ {
assert(self); if (_long_is_small_int(self)) {
if (_PyLong_IsCompact((PyLongObject *)self)) { /* This should never get called, but we also don't want to SEGV if
if (compact_int_is_small(self)) { * we accidentally decref small Ints out of existence. Instead,
/* This should never get called, but we also don't want to SEGV if * since small Ints are immortal, re-set the reference count.
* 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
* */
* See PEP 683, section Accidental De-Immortalizing for details _Py_SetImmortal(self);
*/ return;
_Py_SetImmortal(self); }
return; if (PyLong_CheckExact(self) && _PyLong_IsCompact((PyLongObject *)self)) {
} _Py_FREELIST_FREE(ints, self, PyObject_Free);
if (PyLong_CheckExact(self)) { return;
_Py_FREELIST_FREE(ints, self, PyObject_Free);
return;
}
} }
Py_TYPE(self)->tp_free(self); Py_TYPE(self)->tp_free(self);
} }
@ -6065,7 +6054,7 @@ long_subtype_new(PyTypeObject *type, PyObject *x, PyObject *obase)
return NULL; return NULL;
} }
assert(PyLong_Check(newobj)); 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++) { for (i = 0; i < n; i++) {
newobj->long_value.ob_digit[i] = tmp->long_value.ob_digit[i]; newobj->long_value.ob_digit[i] = tmp->long_value.ob_digit[i];
} }

View file

@ -890,7 +890,7 @@ class PyLongObjectPtr(PyObjectPtr):
def proxyval(self, visited): def proxyval(self, visited):
''' '''
Python's Include/longinterpr.h has this declaration: Python's Include/cpython/longinterpr.h has this declaration:
typedef struct _PyLongValue { typedef struct _PyLongValue {
uintptr_t lv_tag; /* Number of digits, sign and flags */ uintptr_t lv_tag; /* Number of digits, sign and flags */
@ -909,8 +909,7 @@ class PyLongObjectPtr(PyObjectPtr):
- 0: Positive - 0: Positive
- 1: Zero - 1: Zero
- 2: Negative - 2: Negative
The third lowest bit of lv_tag is reserved for an immortality flag, but is The third lowest bit of lv_tag is set to 1 for the small ints and 0 otherwise.
not currently used.
where SHIFT can be either: where SHIFT can be either:
#define PyLong_SHIFT 30 #define PyLong_SHIFT 30