https://github.com/python/cpython/commit/119bcfad9cac9ac8fa5fa6c0f3ee3ccfca778fe1
commit: 119bcfad9cac9ac8fa5fa6c0f3ee3ccfca778fe1
branch: main
author: Chris Eibl <138194463+chris-e...@users.noreply.github.com>
committer: picnixz <10796600+picn...@users.noreply.github.com>
date: 2025-03-13T12:06:56+01:00
summary:

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 <skirpic...@gmail.com>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst
M Lib/test/test_capi/test_long.py
M Objects/longobject.c

diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py
index a371f634432b6d..d3156645eeec2d 100644
--- a/Lib/test/test_capi/test_long.py
+++ b/Lib/test/test_capi/test_long.py
@@ -168,9 +168,9 @@ def check_long_asint(self, func, min_val, max_val, *,
                          mask=False,
                          negative_value_error=OverflowError):
         # round trip (object -> C integer -> object)
-        values = (0, 1, 1234, max_val)
+        values = (0, 1, 512, 1234, max_val)
         if min_val < 0:
-            values += (-1, min_val)
+            values += (-1, -512, -1234, min_val)
         for value in values:
             with self.subTest(value=value):
                 self.assertEqual(func(value), value)
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst
new file mode 100644
index 00000000000000..b74607986d9a2e
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst
@@ -0,0 +1,2 @@
+Add fast path for medium-size integers in :c:func:`PyLong_FromSsize_t`.
+Patch by Chris Eibl.
diff --git a/Objects/longobject.c b/Objects/longobject.c
index ab5cfab74d8a34..51ecbd9e0ee0bd 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -332,49 +332,51 @@ _PyLong_Negate(PyLongObject **x_p)
     Py_DECREF(x);
 }
 
+#define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival)                             
     \
+    do {                                                                       
     \
+        /* Handle small and medium cases. */                                   
     \
+        if (IS_SMALL_INT(ival)) {                                              
     \
+            return get_small_int((sdigit)(ival));                              
     \
+        }                                                                      
     \
+        if (-(INT_TYPE)PyLong_MASK <= (ival) && (ival) <= 
(INT_TYPE)PyLong_MASK) {  \
+            return _PyLong_FromMedium((sdigit)(ival));                         
     \
+        }                                                                      
     \
+        UINT_TYPE abs_ival = (ival) < 0 ? 0U-(UINT_TYPE)(ival) : 
(UINT_TYPE)(ival); \
+        /* Do shift in two steps to avoid possible undefined behavior. */      
     \
+        UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT;                
     \
+        /* Count digits (at least two - smaller cases were handled above). */  
     \
+        Py_ssize_t ndigits = 2;                                                
     \
+        while (t) {                                                            
     \
+            ++ndigits;                                                         
     \
+            t >>= PyLong_SHIFT;                                                
     \
+        }                                                                      
     \
+        /* Construct output value. */                                          
     \
+        PyLongObject *v = long_alloc(ndigits);                                 
     \
+        if (v == NULL) {                                                       
     \
+            return NULL;                                                       
     \
+        }                                                                      
     \
+        digit *p = v->long_value.ob_digit;                                     
     \
+        _PyLong_SetSignAndDigitCount(v, (ival) < 0 ? -1 : 1, ndigits);         
     \
+        t = abs_ival;                                                          
     \
+        while (t) {                                                            
     \
+            *p++ = (digit)(t & PyLong_MASK);                                   
     \
+            t >>= PyLong_SHIFT;                                                
     \
+        }                                                                      
     \
+        return (PyObject *)v;                                                  
     \
+    } while(0)
+
+
 /* Create a new int object from a C long int */
 
 PyObject *
 PyLong_FromLong(long ival)
 {
-    PyLongObject *v;
-    unsigned long abs_ival, t;
-    int ndigits;
-
-    /* Handle small and medium cases. */
-    if (IS_SMALL_INT(ival)) {
-        return get_small_int((sdigit)ival);
-    }
-    if (-(long)PyLong_MASK <= ival && ival <= (long)PyLong_MASK) {
-        return _PyLong_FromMedium((sdigit)ival);
-    }
-
-    /* Count digits (at least two - smaller cases were handled above). */
-    abs_ival = ival < 0 ? 0U-(unsigned long)ival : (unsigned long)ival;
-    /* Do shift in two steps to avoid possible undefined behavior. */
-    t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT;
-    ndigits = 2;
-    while (t) {
-        ++ndigits;
-        t >>= PyLong_SHIFT;
-    }
-
-    /* Construct output value. */
-    v = long_alloc(ndigits);
-    if (v != NULL) {
-        digit *p = v->long_value.ob_digit;
-        _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits);
-        t = abs_ival;
-        while (t) {
-            *p++ = (digit)(t & PyLong_MASK);
-            t >>= PyLong_SHIFT;
-        }
-    }
-    return (PyObject *)v;
+    PYLONG_FROM_INT(unsigned long, long, ival);
 }
 
 #define PYLONG_FROM_UINT(INT_TYPE, ival) \
     do { \
+        /* Handle small and medium cases. */ \
         if (IS_SMALL_UINT(ival)) { \
             return get_small_int((sdigit)(ival)); \
         } \
@@ -383,12 +385,13 @@ PyLong_FromLong(long ival)
         } \
         /* Do shift in two steps to avoid possible undefined behavior. */ \
         INT_TYPE t = (ival) >> PyLong_SHIFT >> PyLong_SHIFT; \
-        /* Count the number of Python digits. */ \
+        /* Count digits (at least two - smaller cases were handled above). */ \
         Py_ssize_t ndigits = 2; \
         while (t) { \
             ++ndigits; \
             t >>= PyLong_SHIFT; \
         } \
+        /* Construct output value. */ \
         PyLongObject *v = long_alloc(ndigits); \
         if (v == NULL) { \
             return NULL; \
@@ -1482,40 +1485,7 @@ PyLong_AsVoidPtr(PyObject *vv)
 PyObject *
 PyLong_FromLongLong(long long ival)
 {
-    PyLongObject *v;
-    unsigned long long abs_ival, t;
-    int ndigits;
-
-    /* Handle small and medium cases. */
-    if (IS_SMALL_INT(ival)) {
-        return get_small_int((sdigit)ival);
-    }
-    if (-(long long)PyLong_MASK <= ival && ival <= (long long)PyLong_MASK) {
-        return _PyLong_FromMedium((sdigit)ival);
-    }
-
-    /* Count digits (at least two - smaller cases were handled above). */
-    abs_ival = ival < 0 ? 0U-(unsigned long long)ival : (unsigned long 
long)ival;
-    /* Do shift in two steps to avoid possible undefined behavior. */
-    t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT;
-    ndigits = 2;
-    while (t) {
-        ++ndigits;
-        t >>= PyLong_SHIFT;
-    }
-
-    /* Construct output value. */
-    v = long_alloc(ndigits);
-    if (v != NULL) {
-        digit *p = v->long_value.ob_digit;
-        _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits);
-        t = abs_ival;
-        while (t) {
-            *p++ = (digit)(t & PyLong_MASK);
-            t >>= PyLong_SHIFT;
-        }
-    }
-    return (PyObject *)v;
+    PYLONG_FROM_INT(unsigned long long, long long, ival);
 }
 
 /* Create a new int object from a C Py_ssize_t. */
@@ -1523,42 +1493,7 @@ PyLong_FromLongLong(long long ival)
 PyObject *
 PyLong_FromSsize_t(Py_ssize_t ival)
 {
-    PyLongObject *v;
-    size_t abs_ival;
-    size_t t;  /* unsigned so >> doesn't propagate sign bit */
-    int ndigits = 0;
-    int negative = 0;
-
-    if (IS_SMALL_INT(ival)) {
-        return get_small_int((sdigit)ival);
-    }
-
-    if (ival < 0) {
-        /* avoid signed overflow when ival = SIZE_T_MIN */
-        abs_ival = (size_t)(-1-ival)+1;
-        negative = 1;
-    }
-    else {
-        abs_ival = (size_t)ival;
-    }
-
-    /* Count the number of Python digits. */
-    t = abs_ival;
-    while (t) {
-        ++ndigits;
-        t >>= PyLong_SHIFT;
-    }
-    v = long_alloc(ndigits);
-    if (v != NULL) {
-        digit *p = v->long_value.ob_digit;
-        _PyLong_SetSignAndDigitCount(v, negative ? -1 : 1, ndigits);
-        t = abs_ival;
-        while (t) {
-            *p++ = (digit)(t & PyLong_MASK);
-            t >>= PyLong_SHIFT;
-        }
-    }
-    return (PyObject *)v;
+    PYLONG_FROM_INT(size_t, Py_ssize_t, ival);
 }
 
 /* Get a C long long int from an int object or any object that has an

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to