Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: gh-127119: Faster check for small ints in long_dealloc (debug) #128187

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
cfb70cb
Faster check for small ints in long_dealloc
eendebakpt Dec 4, 2024
426dd09
📜🤖 Added by blurb_it.
blurb-it[bot] Dec 4, 2024
4639642
Use int immortality bit for small int check
eendebakpt Dec 5, 2024
d9c26d3
Merge branch 'small_int_immortal_v2' of github.com:eendebakpt/cpython…
eendebakpt Dec 5, 2024
2f73d47
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 5, 2024
3b6e1fe
fix compiler warnings
eendebakpt Dec 5, 2024
03184a7
Merge branch 'small_int_immortal_v2' of github.com:eendebakpt/cpython…
eendebakpt Dec 5, 2024
5eca812
compiler warnings
eendebakpt Dec 5, 2024
fedc102
some documentation updates
eendebakpt Dec 5, 2024
c733d25
Update Objects/longobject.c
eendebakpt Dec 5, 2024
829a595
Merge branch 'small_int_immortal_v2' of github.com:eendebakpt/cpython…
eendebakpt Dec 5, 2024
39da0ea
Update Tools/gdb/libpython.py
eendebakpt Dec 5, 2024
5058e53
tests!
eendebakpt Dec 5, 2024
8a3d00f
tests!
eendebakpt Dec 5, 2024
878207a
update _PyLong_IsNonNegativeCompact
eendebakpt Dec 5, 2024
068a16a
Apply suggestions from code review
eendebakpt Dec 11, 2024
a65ec5a
review comments
eendebakpt Dec 11, 2024
38fe25f
Merge branch 'small_int_immortal_v2' of github.com:eendebakpt/cpython…
eendebakpt Dec 11, 2024
32b6e44
spacing
eendebakpt Dec 11, 2024
8543c78
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 11, 2024
e232ca4
Update Tools/gdb/libpython.py
eendebakpt Dec 11, 2024
f1ce753
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 13, 2024
99a2fc7
whitespace
eendebakpt Dec 13, 2024
f6a76b0
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 17, 2024
9894866
Apply suggestions from code review
eendebakpt Dec 17, 2024
7834406
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 18, 2024
d91b6e3
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 20, 2024
ebc7e17
Update Modules/_testcapi/immortal.c
eendebakpt Dec 22, 2024
aaf110b
Update Modules/_testcapi/immortal.c
eendebakpt Dec 22, 2024
8fcc1d0
Update Include/cpython/longintrepr.h
eendebakpt Dec 22, 2024
aff8812
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 22, 2024
ebb0bca
review comments
eendebakpt Dec 22, 2024
8d8e794
include pycore_long.h
eendebakpt Dec 22, 2024
4a07ba1
header
eendebakpt Dec 22, 2024
1d5b2e0
include pycore_long.h
eendebakpt Dec 22, 2024
cfb9120
debug build failure
eendebakpt Dec 22, 2024
b606c09
Merge branch 'main' into small_int_immortal_v2
eendebakpt Dec 23, 2024
b4c8444
fix bug when copying the immortal bit
eendebakpt Dec 23, 2024
3a553d1
test small int test
eendebakpt Dec 23, 2024
62939d1
print
eendebakpt Dec 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Include/cpython/longintrepr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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));
}


Expand Down Expand Up @@ -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)) }, \
} \
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Slightly optimize the :class:`int` deallocator.
15 changes: 14 additions & 1 deletion Modules/_testcapi/immortal.c
Original file line number Diff line number Diff line change
@@ -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));
Expand All @@ -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;
}
Expand Down
65 changes: 29 additions & 36 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3616,32 +3616,23 @@
}

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;
return (long_object->long_value.lv_tag & IMMORTALITY_BIT_MASK) != 0;
}

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;
}
Expand All @@ -3651,24 +3642,26 @@
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;
}
// test what happens if we do hit the "never should get called": we should expect memory leaks, but no crashes
PyLongObject *pylong = (PyLongObject *)self;
int dotest = PyLong_CheckExact(self) && _PyLong_IsCompact(pylong) && (medium_value(pylong)==5);

if (_long_is_small_int(self) || dotest) {
/* 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
*/
//assert(0);
printf("call _Py_SetImmortal on %d: %d\n", PyLong_CheckExact(self), medium_value(pylong));

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘long int’ [-Wformat=]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘long int’ [-Wformat=]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows / build and test (arm64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'stwodigits' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows / build and test (arm64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'Py_ssize_t' [D:\a\cpython\cpython\PCbuild\pythoncore.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows (free-threading) / build and test (arm64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'stwodigits' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows (free-threading) / build and test (arm64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'Py_ssize_t' [D:\a\cpython\cpython\PCbuild\pythoncore.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows / build and test (x64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'stwodigits' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows / build and test (x64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'Py_ssize_t' [D:\a\cpython\cpython\PCbuild\pythoncore.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows (free-threading) / build and test (x64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'stwodigits' [D:\a\cpython\cpython\PCbuild\_freeze_module.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Windows (free-threading) / build and test (x64)

'printf' : format string '%d' requires an argument of type 'int', but variadic argument 2 has type 'Py_ssize_t' [D:\a\cpython\cpython\PCbuild\pythoncore.vcxproj]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘long int’ [-Wformat=]

Check warning on line 3657 in Objects/longobject.c

View workflow job for this annotation

GitHub Actions / Ubuntu (free-threading) / build and test (ubuntu-24.04)

format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘long int’ [-Wformat=]
_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);
}

Expand Down Expand Up @@ -6030,7 +6023,7 @@
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];
}
Expand Down
5 changes: 2 additions & 3 deletions Tools/gdb/libpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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
Expand Down
Loading