Skip to content

Commit 119bcfa

Browse files
gh-129149: Add fast path for medium-sized integers in PyLong_FromSsize_t() (#129301)
The implementation of `PyLong_FromLong()`, `PyLong_FromLongLong()` and `PyLong_FromSsize_t()` are now handled by a common macro `PYLONG_FROM_INT` which contains fast paths depending on the size of the integer to convert. Consequently, `PyLong_FromSsize_t()` for medium-sized integers is faster by roughly 25%. --------- Co-authored-by: Sergey B Kirpichev <[email protected]>
1 parent a005835 commit 119bcfa

File tree

3 files changed

+44
-107
lines changed

3 files changed

+44
-107
lines changed

Lib/test/test_capi/test_long.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,9 @@ def check_long_asint(self, func, min_val, max_val, *,
168168
mask=False,
169169
negative_value_error=OverflowError):
170170
# round trip (object -> C integer -> object)
171-
values = (0, 1, 1234, max_val)
171+
values = (0, 1, 512, 1234, max_val)
172172
if min_val < 0:
173-
values += (-1, min_val)
173+
values += (-1, -512, -1234, min_val)
174174
for value in values:
175175
with self.subTest(value=value):
176176
self.assertEqual(func(value), value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add fast path for medium-size integers in :c:func:`PyLong_FromSsize_t`.
2+
Patch by Chris Eibl.

Objects/longobject.c

+40-105
Original file line numberDiff line numberDiff line change
@@ -332,49 +332,51 @@ _PyLong_Negate(PyLongObject **x_p)
332332
Py_DECREF(x);
333333
}
334334

335+
#define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival) \
336+
do { \
337+
/* Handle small and medium cases. */ \
338+
if (IS_SMALL_INT(ival)) { \
339+
return get_small_int((sdigit)(ival)); \
340+
} \
341+
if (-(INT_TYPE)PyLong_MASK <= (ival) && (ival) <= (INT_TYPE)PyLong_MASK) { \
342+
return _PyLong_FromMedium((sdigit)(ival)); \
343+
} \
344+
UINT_TYPE abs_ival = (ival) < 0 ? 0U-(UINT_TYPE)(ival) : (UINT_TYPE)(ival); \
345+
/* Do shift in two steps to avoid possible undefined behavior. */ \
346+
UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \
347+
/* Count digits (at least two - smaller cases were handled above). */ \
348+
Py_ssize_t ndigits = 2; \
349+
while (t) { \
350+
++ndigits; \
351+
t >>= PyLong_SHIFT; \
352+
} \
353+
/* Construct output value. */ \
354+
PyLongObject *v = long_alloc(ndigits); \
355+
if (v == NULL) { \
356+
return NULL; \
357+
} \
358+
digit *p = v->long_value.ob_digit; \
359+
_PyLong_SetSignAndDigitCount(v, (ival) < 0 ? -1 : 1, ndigits); \
360+
t = abs_ival; \
361+
while (t) { \
362+
*p++ = (digit)(t & PyLong_MASK); \
363+
t >>= PyLong_SHIFT; \
364+
} \
365+
return (PyObject *)v; \
366+
} while(0)
367+
368+
335369
/* Create a new int object from a C long int */
336370

337371
PyObject *
338372
PyLong_FromLong(long ival)
339373
{
340-
PyLongObject *v;
341-
unsigned long abs_ival, t;
342-
int ndigits;
343-
344-
/* Handle small and medium cases. */
345-
if (IS_SMALL_INT(ival)) {
346-
return get_small_int((sdigit)ival);
347-
}
348-
if (-(long)PyLong_MASK <= ival && ival <= (long)PyLong_MASK) {
349-
return _PyLong_FromMedium((sdigit)ival);
350-
}
351-
352-
/* Count digits (at least two - smaller cases were handled above). */
353-
abs_ival = ival < 0 ? 0U-(unsigned long)ival : (unsigned long)ival;
354-
/* Do shift in two steps to avoid possible undefined behavior. */
355-
t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT;
356-
ndigits = 2;
357-
while (t) {
358-
++ndigits;
359-
t >>= PyLong_SHIFT;
360-
}
361-
362-
/* Construct output value. */
363-
v = long_alloc(ndigits);
364-
if (v != NULL) {
365-
digit *p = v->long_value.ob_digit;
366-
_PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits);
367-
t = abs_ival;
368-
while (t) {
369-
*p++ = (digit)(t & PyLong_MASK);
370-
t >>= PyLong_SHIFT;
371-
}
372-
}
373-
return (PyObject *)v;
374+
PYLONG_FROM_INT(unsigned long, long, ival);
374375
}
375376

376377
#define PYLONG_FROM_UINT(INT_TYPE, ival) \
377378
do { \
379+
/* Handle small and medium cases. */ \
378380
if (IS_SMALL_UINT(ival)) { \
379381
return get_small_int((sdigit)(ival)); \
380382
} \
@@ -383,12 +385,13 @@ PyLong_FromLong(long ival)
383385
} \
384386
/* Do shift in two steps to avoid possible undefined behavior. */ \
385387
INT_TYPE t = (ival) >> PyLong_SHIFT >> PyLong_SHIFT; \
386-
/* Count the number of Python digits. */ \
388+
/* Count digits (at least two - smaller cases were handled above). */ \
387389
Py_ssize_t ndigits = 2; \
388390
while (t) { \
389391
++ndigits; \
390392
t >>= PyLong_SHIFT; \
391393
} \
394+
/* Construct output value. */ \
392395
PyLongObject *v = long_alloc(ndigits); \
393396
if (v == NULL) { \
394397
return NULL; \
@@ -1482,83 +1485,15 @@ PyLong_AsVoidPtr(PyObject *vv)
14821485
PyObject *
14831486
PyLong_FromLongLong(long long ival)
14841487
{
1485-
PyLongObject *v;
1486-
unsigned long long abs_ival, t;
1487-
int ndigits;
1488-
1489-
/* Handle small and medium cases. */
1490-
if (IS_SMALL_INT(ival)) {
1491-
return get_small_int((sdigit)ival);
1492-
}
1493-
if (-(long long)PyLong_MASK <= ival && ival <= (long long)PyLong_MASK) {
1494-
return _PyLong_FromMedium((sdigit)ival);
1495-
}
1496-
1497-
/* Count digits (at least two - smaller cases were handled above). */
1498-
abs_ival = ival < 0 ? 0U-(unsigned long long)ival : (unsigned long long)ival;
1499-
/* Do shift in two steps to avoid possible undefined behavior. */
1500-
t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT;
1501-
ndigits = 2;
1502-
while (t) {
1503-
++ndigits;
1504-
t >>= PyLong_SHIFT;
1505-
}
1506-
1507-
/* Construct output value. */
1508-
v = long_alloc(ndigits);
1509-
if (v != NULL) {
1510-
digit *p = v->long_value.ob_digit;
1511-
_PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits);
1512-
t = abs_ival;
1513-
while (t) {
1514-
*p++ = (digit)(t & PyLong_MASK);
1515-
t >>= PyLong_SHIFT;
1516-
}
1517-
}
1518-
return (PyObject *)v;
1488+
PYLONG_FROM_INT(unsigned long long, long long, ival);
15191489
}
15201490

15211491
/* Create a new int object from a C Py_ssize_t. */
15221492

15231493
PyObject *
15241494
PyLong_FromSsize_t(Py_ssize_t ival)
15251495
{
1526-
PyLongObject *v;
1527-
size_t abs_ival;
1528-
size_t t; /* unsigned so >> doesn't propagate sign bit */
1529-
int ndigits = 0;
1530-
int negative = 0;
1531-
1532-
if (IS_SMALL_INT(ival)) {
1533-
return get_small_int((sdigit)ival);
1534-
}
1535-
1536-
if (ival < 0) {
1537-
/* avoid signed overflow when ival = SIZE_T_MIN */
1538-
abs_ival = (size_t)(-1-ival)+1;
1539-
negative = 1;
1540-
}
1541-
else {
1542-
abs_ival = (size_t)ival;
1543-
}
1544-
1545-
/* Count the number of Python digits. */
1546-
t = abs_ival;
1547-
while (t) {
1548-
++ndigits;
1549-
t >>= PyLong_SHIFT;
1550-
}
1551-
v = long_alloc(ndigits);
1552-
if (v != NULL) {
1553-
digit *p = v->long_value.ob_digit;
1554-
_PyLong_SetSignAndDigitCount(v, negative ? -1 : 1, ndigits);
1555-
t = abs_ival;
1556-
while (t) {
1557-
*p++ = (digit)(t & PyLong_MASK);
1558-
t >>= PyLong_SHIFT;
1559-
}
1560-
}
1561-
return (PyObject *)v;
1496+
PYLONG_FROM_INT(size_t, Py_ssize_t, ival);
15621497
}
15631498

15641499
/* Get a C long long int from an int object or any object that has an

0 commit comments

Comments
 (0)