Skip to content

Commit a292216

Browse files
authored
gh-127119: Faster check for small ints in long_dealloc (GH-127620)
1 parent 3a974e3 commit a292216

File tree

6 files changed

+48
-45
lines changed

6 files changed

+48
-45
lines changed

Include/cpython/longintrepr.h

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

Include/internal/pycore_long.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,14 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void *);
159159

160160
/* Long value tag bits:
161161
* 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1.
162-
* 2: Reserved for immortality bit
162+
* 2: Set to 1 for the small ints
163163
* 3+ Unsigned digit count
164164
*/
165165
#define SIGN_MASK 3
166166
#define SIGN_ZERO 1
167167
#define SIGN_NEGATIVE 2
168168
#define NON_SIZE_BITS 3
169+
#define IMMORTALITY_BIT_MASK (1 << 2)
169170

170171
/* The functions _PyLong_IsCompact and _PyLong_CompactValue are defined
171172
* in Include/cpython/longobject.h, since they need to be inline.
@@ -196,7 +197,7 @@ PyAPI_FUNC(int) _PyLong_Size_t_Converter(PyObject *, void *);
196197
static inline int
197198
_PyLong_IsNonNegativeCompact(const PyLongObject* op) {
198199
assert(PyLong_Check(op));
199-
return op->long_value.lv_tag <= (1 << NON_SIZE_BITS);
200+
return ((op->long_value.lv_tag & ~IMMORTALITY_BIT_MASK) <= (1 << NON_SIZE_BITS));
200201
}
201202

202203

@@ -298,7 +299,7 @@ _PyLong_FlipSign(PyLongObject *op) {
298299
.long_value = { \
299300
.lv_tag = TAG_FROM_SIGN_AND_SIZE( \
300301
(val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \
301-
(val) == 0 ? 0 : 1), \
302+
(val) == 0 ? 0 : 1) | IMMORTALITY_BIT_MASK, \
302303
{ ((val) >= 0 ? (val) : -(val)) }, \
303304
} \
304305
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Slightly optimize the :class:`int` deallocator.

Modules/_testcapi/immortal.c

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "parts.h"
22

3+
#define Py_BUILD_CORE
4+
#include "internal/pycore_long.h" // IMMORTALITY_BIT_MASK
5+
36
int verify_immortality(PyObject *object)
47
{
58
assert(_Py_IsImmortal(object));
@@ -26,7 +29,17 @@ static PyObject *
2629
test_immortal_small_ints(PyObject *self, PyObject *Py_UNUSED(ignored))
2730
{
2831
for (int i = -5; i <= 256; i++) {
29-
assert(verify_immortality(PyLong_FromLong(i)));
32+
PyObject *obj = PyLong_FromLong(i);
33+
assert(verify_immortality(obj));
34+
int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK;
35+
assert(has_int_immortal_bit);
36+
}
37+
for (int i = 257; i <= 260; i++) {
38+
PyObject *obj = PyLong_FromLong(i);
39+
assert(obj);
40+
int has_int_immortal_bit = ((PyLongObject *)obj)->long_value.lv_tag & IMMORTALITY_BIT_MASK;
41+
assert(!has_int_immortal_bit);
42+
Py_DECREF(obj);
3043
}
3144
Py_RETURN_NONE;
3245
}

Objects/longobject.c

+25-36
Original file line numberDiff line numberDiff line change
@@ -3651,32 +3651,25 @@ long_richcompare(PyObject *self, PyObject *other, int op)
36513651
}
36523652

36533653
static inline int
3654-
compact_int_is_small(PyObject *self)
3654+
/// Return 1 if the object is one of the immortal small ints
3655+
_long_is_small_int(PyObject *op)
36553656
{
3656-
PyLongObject *pylong = (PyLongObject *)self;
3657-
assert(_PyLong_IsCompact(pylong));
3658-
stwodigits ival = medium_value(pylong);
3659-
if (IS_SMALL_INT(ival)) {
3660-
PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival);
3661-
if (pylong == small_pylong) {
3662-
return 1;
3663-
}
3664-
}
3665-
return 0;
3657+
PyLongObject *long_object = (PyLongObject *)op;
3658+
int is_small_int = (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
3659+
assert((!is_small_int) || PyLong_CheckExact(op));
3660+
return is_small_int;
36663661
}
36673662

36683663
void
36693664
_PyLong_ExactDealloc(PyObject *self)
36703665
{
36713666
assert(PyLong_CheckExact(self));
3667+
if (_long_is_small_int(self)) {
3668+
// See PEP 683, section Accidental De-Immortalizing for details
3669+
_Py_SetImmortal(self);
3670+
return;
3671+
}
36723672
if (_PyLong_IsCompact((PyLongObject *)self)) {
3673-
#ifndef Py_GIL_DISABLED
3674-
if (compact_int_is_small(self)) {
3675-
// See PEP 683, section Accidental De-Immortalizing for details
3676-
_Py_SetImmortal(self);
3677-
return;
3678-
}
3679-
#endif
36803673
_Py_FREELIST_FREE(ints, self, PyObject_Free);
36813674
return;
36823675
}
@@ -3686,24 +3679,20 @@ _PyLong_ExactDealloc(PyObject *self)
36863679
static void
36873680
long_dealloc(PyObject *self)
36883681
{
3689-
assert(self);
3690-
if (_PyLong_IsCompact((PyLongObject *)self)) {
3691-
if (compact_int_is_small(self)) {
3692-
/* This should never get called, but we also don't want to SEGV if
3693-
* we accidentally decref small Ints out of existence. Instead,
3694-
* since small Ints are immortal, re-set the reference count.
3695-
*
3696-
* See PEP 683, section Accidental De-Immortalizing for details
3697-
*/
3698-
_Py_SetImmortal(self);
3699-
return;
3700-
}
3701-
if (PyLong_CheckExact(self)) {
3702-
_Py_FREELIST_FREE(ints, self, PyObject_Free);
3703-
return;
3704-
}
3682+
if (_long_is_small_int(self)) {
3683+
/* This should never get called, but we also don't want to SEGV if
3684+
* we accidentally decref small Ints out of existence. Instead,
3685+
* since small Ints are immortal, re-set the reference count.
3686+
*
3687+
* See PEP 683, section Accidental De-Immortalizing for details
3688+
*/
3689+
_Py_SetImmortal(self);
3690+
return;
3691+
}
3692+
if (PyLong_CheckExact(self) && _PyLong_IsCompact((PyLongObject *)self)) {
3693+
_Py_FREELIST_FREE(ints, self, PyObject_Free);
3694+
return;
37053695
}
3706-
37073696
Py_TYPE(self)->tp_free(self);
37083697
}
37093698

@@ -6065,7 +6054,7 @@ long_subtype_new(PyTypeObject *type, PyObject *x, PyObject *obase)
60656054
return NULL;
60666055
}
60676056
assert(PyLong_Check(newobj));
6068-
newobj->long_value.lv_tag = tmp->long_value.lv_tag;
6057+
newobj->long_value.lv_tag = tmp->long_value.lv_tag & ~IMMORTALITY_BIT_MASK;
60696058
for (i = 0; i < n; i++) {
60706059
newobj->long_value.ob_digit[i] = tmp->long_value.ob_digit[i];
60716060
}

Tools/gdb/libpython.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ class PyLongObjectPtr(PyObjectPtr):
890890

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

0 commit comments

Comments
 (0)