Skip to content

Commit 15d50d7

Browse files
authored
bpo-44946: Streamline operators and creation of ints for common case of single 'digit'. (GH-27832)
1 parent f9242d5 commit 15d50d7

File tree

1 file changed

+160
-92
lines changed

1 file changed

+160
-92
lines changed

Objects/longobject.c

+160-92
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,27 @@ class int "PyObject *" "&PyLong_Type"
2626
_Py_IDENTIFIER(little);
2727
_Py_IDENTIFIER(big);
2828

29-
/* convert a PyLong of size 1, 0 or -1 to an sdigit */
30-
#define MEDIUM_VALUE(x) (assert(-1 <= Py_SIZE(x) && Py_SIZE(x) <= 1), \
31-
Py_SIZE(x) < 0 ? -(sdigit)(x)->ob_digit[0] : \
32-
(Py_SIZE(x) == 0 ? (sdigit)0 : \
33-
(sdigit)(x)->ob_digit[0]))
29+
/* Is this PyLong of size 1, 0 or -1? */
30+
#define IS_MEDIUM_VALUE(x) (((size_t)Py_SIZE(x)) + 1U < 3U)
31+
32+
/* convert a PyLong of size 1, 0 or -1 to a C integer */
33+
static inline stwodigits
34+
medium_value(PyLongObject *x)
35+
{
36+
assert(IS_MEDIUM_VALUE(x));
37+
return ((stwodigits)Py_SIZE(x)) * x->ob_digit[0];
38+
}
3439

3540
#define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS)
3641
#define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS)
3742

43+
static inline int is_medium_int(stwodigits x)
44+
{
45+
/* Take care that we are comparing unsigned values. */
46+
twodigits x_plus_mask = ((twodigits)x) + PyLong_MASK;
47+
return x_plus_mask < ((twodigits)PyLong_MASK) + PyLong_BASE;
48+
}
49+
3850
static PyObject *
3951
get_small_int(sdigit ival)
4052
{
@@ -47,33 +59,16 @@ get_small_int(sdigit ival)
4759
static PyLongObject *
4860
maybe_small_long(PyLongObject *v)
4961
{
50-
if (v && Py_ABS(Py_SIZE(v)) <= 1) {
51-
sdigit ival = MEDIUM_VALUE(v);
62+
if (v && IS_MEDIUM_VALUE(v)) {
63+
stwodigits ival = medium_value(v);
5264
if (IS_SMALL_INT(ival)) {
5365
Py_DECREF(v);
54-
return (PyLongObject *)get_small_int(ival);
66+
return (PyLongObject *)get_small_int((sdigit)ival);
5567
}
5668
}
5769
return v;
5870
}
5971

60-
/* If a freshly-allocated int is already shared, it must
61-
be a small integer, so negating it must go to PyLong_FromLong */
62-
Py_LOCAL_INLINE(void)
63-
_PyLong_Negate(PyLongObject **x_p)
64-
{
65-
PyLongObject *x;
66-
67-
x = (PyLongObject *)*x_p;
68-
if (Py_REFCNT(x) == 1) {
69-
Py_SET_SIZE(x, -Py_SIZE(x));
70-
return;
71-
}
72-
73-
*x_p = (PyLongObject *)PyLong_FromLong(-MEDIUM_VALUE(x));
74-
Py_DECREF(x);
75-
}
76-
7772
/* For int multiplication, use the O(N**2) school algorithm unless
7873
* both operands contain more than KARATSUBA_CUTOFF digits (this
7974
* being an internal Python int digit, in base BASE).
@@ -121,18 +116,21 @@ PyLongObject *
121116
_PyLong_New(Py_ssize_t size)
122117
{
123118
PyLongObject *result;
124-
/* Number of bytes needed is: offsetof(PyLongObject, ob_digit) +
125-
sizeof(digit)*size. Previous incarnations of this code used
126-
sizeof(PyVarObject) instead of the offsetof, but this risks being
127-
incorrect in the presence of padding between the PyVarObject header
128-
and the digits. */
129119
if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
130120
PyErr_SetString(PyExc_OverflowError,
131121
"too many digits in integer");
132122
return NULL;
133123
}
124+
/* Fast operations for single digit integers (including zero)
125+
* assume that there is always at least one digit present. */
126+
Py_ssize_t ndigits = size ? size : 1;
127+
/* Number of bytes needed is: offsetof(PyLongObject, ob_digit) +
128+
sizeof(digit)*size. Previous incarnations of this code used
129+
sizeof(PyVarObject) instead of the offsetof, but this risks being
130+
incorrect in the presence of padding between the PyVarObject header
131+
and the digits. */
134132
result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) +
135-
size*sizeof(digit));
133+
ndigits*sizeof(digit));
136134
if (!result) {
137135
PyErr_NoMemory();
138136
return NULL;
@@ -152,9 +150,9 @@ _PyLong_Copy(PyLongObject *src)
152150
if (i < 0)
153151
i = -(i);
154152
if (i < 2) {
155-
sdigit ival = MEDIUM_VALUE(src);
153+
stwodigits ival = medium_value(src);
156154
if (IS_SMALL_INT(ival)) {
157-
return get_small_int(ival);
155+
return get_small_int((sdigit)ival);
158156
}
159157
}
160158
result = _PyLong_New(i);
@@ -167,65 +165,126 @@ _PyLong_Copy(PyLongObject *src)
167165
return (PyObject *)result;
168166
}
169167

170-
/* Create a new int object from a C long int */
168+
static PyObject *
169+
_PyLong_FromMedium(sdigit x)
170+
{
171+
assert(!IS_SMALL_INT(x));
172+
assert(is_medium_int(x));
173+
/* We could use a freelist here */
174+
PyLongObject *v = PyObject_Malloc(sizeof(PyLongObject));
175+
if (v == NULL) {
176+
PyErr_NoMemory();
177+
return NULL;
178+
}
179+
Py_ssize_t sign = x < 0 ? -1: 1;
180+
digit abs_x = x < 0 ? -x : x;
181+
_PyObject_InitVar((PyVarObject*)v, &PyLong_Type, sign);
182+
v->ob_digit[0] = abs_x;
183+
return (PyObject*)v;
184+
}
171185

172-
PyObject *
173-
PyLong_FromLong(long ival)
186+
static PyObject *
187+
_PyLong_FromLarge(stwodigits ival)
174188
{
175-
PyLongObject *v;
176-
unsigned long abs_ival;
177-
unsigned long t; /* unsigned so >> doesn't propagate sign bit */
178-
int ndigits = 0;
189+
twodigits abs_ival;
179190
int sign;
191+
assert(!is_medium_int(ival));
180192

193+
if (ival < 0) {
194+
/* negate: can't write this as abs_ival = -ival since that
195+
invokes undefined behaviour when ival is LONG_MIN */
196+
abs_ival = 0U-(twodigits)ival;
197+
sign = -1;
198+
}
199+
else {
200+
abs_ival = (twodigits)ival;
201+
sign = 1;
202+
}
203+
/* Must be at least two digits */
204+
assert(abs_ival >> PyLong_SHIFT != 0);
205+
twodigits t = abs_ival >> (PyLong_SHIFT * 2);
206+
Py_ssize_t ndigits = 2;
207+
while (t) {
208+
++ndigits;
209+
t >>= PyLong_SHIFT;
210+
}
211+
PyLongObject *v = _PyLong_New(ndigits);
212+
if (v != NULL) {
213+
digit *p = v->ob_digit;
214+
Py_SET_SIZE(v, ndigits * sign);
215+
t = abs_ival;
216+
while (t) {
217+
*p++ = Py_SAFE_DOWNCAST(
218+
t & PyLong_MASK, twodigits, digit);
219+
t >>= PyLong_SHIFT;
220+
}
221+
}
222+
return (PyObject *)v;
223+
}
224+
225+
/* Create a new int object from a C word-sized int */
226+
static inline PyObject *
227+
_PyLong_FromSTwoDigits(stwodigits x)
228+
{
229+
if (IS_SMALL_INT(x)) {
230+
return get_small_int((sdigit)x);
231+
}
232+
assert(x != 0);
233+
if (is_medium_int(x)) {
234+
return _PyLong_FromMedium((sdigit)x);
235+
}
236+
return _PyLong_FromLarge(x);
237+
}
238+
239+
/* If a freshly-allocated int is already shared, it must
240+
be a small integer, so negating it must go to PyLong_FromLong */
241+
Py_LOCAL_INLINE(void)
242+
_PyLong_Negate(PyLongObject **x_p)
243+
{
244+
PyLongObject *x;
245+
246+
x = (PyLongObject *)*x_p;
247+
if (Py_REFCNT(x) == 1) {
248+
Py_SET_SIZE(x, -Py_SIZE(x));
249+
return;
250+
}
251+
252+
*x_p = (PyLongObject *)_PyLong_FromSTwoDigits(-medium_value(x));
253+
Py_DECREF(x);
254+
}
255+
256+
/* Create a new int object from a C long int */
257+
PyObject *
258+
PyLong_FromLong(long ival)
259+
{
181260
if (IS_SMALL_INT(ival)) {
182261
return get_small_int((sdigit)ival);
183262
}
184-
263+
unsigned long abs_ival;
264+
int sign;
185265
if (ival < 0) {
186266
/* negate: can't write this as abs_ival = -ival since that
187267
invokes undefined behaviour when ival is LONG_MIN */
188-
abs_ival = 0U-(unsigned long)ival;
268+
abs_ival = 0U-(twodigits)ival;
189269
sign = -1;
190270
}
191271
else {
192272
abs_ival = (unsigned long)ival;
193-
sign = ival == 0 ? 0 : 1;
273+
sign = 1;
194274
}
195-
196275
/* Fast path for single-digit ints */
197276
if (!(abs_ival >> PyLong_SHIFT)) {
198-
v = _PyLong_New(1);
199-
if (v) {
200-
Py_SET_SIZE(v, sign);
201-
v->ob_digit[0] = Py_SAFE_DOWNCAST(
202-
abs_ival, unsigned long, digit);
203-
}
204-
return (PyObject*)v;
205-
}
206-
207-
#if PyLong_SHIFT==15
208-
/* 2 digits */
209-
if (!(abs_ival >> 2*PyLong_SHIFT)) {
210-
v = _PyLong_New(2);
211-
if (v) {
212-
Py_SET_SIZE(v, 2 * sign);
213-
v->ob_digit[0] = Py_SAFE_DOWNCAST(
214-
abs_ival & PyLong_MASK, unsigned long, digit);
215-
v->ob_digit[1] = Py_SAFE_DOWNCAST(
216-
abs_ival >> PyLong_SHIFT, unsigned long, digit);
217-
}
218-
return (PyObject*)v;
277+
return _PyLong_FromMedium((sdigit)ival);
219278
}
220-
#endif
221-
222-
/* Larger numbers: loop to determine number of digits */
223-
t = abs_ival;
279+
/* Must be at least two digits.
280+
* Do shift in two steps to avoid undefined behavior. */
281+
unsigned long t = (abs_ival >> PyLong_SHIFT) >> PyLong_SHIFT;
282+
Py_ssize_t ndigits = 2;
224283
while (t) {
225284
++ndigits;
226285
t >>= PyLong_SHIFT;
227286
}
228-
v = _PyLong_New(ndigits);
287+
PyLongObject *v = _PyLong_New(ndigits);
229288
if (v != NULL) {
230289
digit *p = v->ob_digit;
231290
Py_SET_SIZE(v, ndigits * sign);
@@ -2860,12 +2919,12 @@ PyLong_AsDouble(PyObject *v)
28602919
PyErr_SetString(PyExc_TypeError, "an integer is required");
28612920
return -1.0;
28622921
}
2863-
if (Py_ABS(Py_SIZE(v)) <= 1) {
2922+
if (IS_MEDIUM_VALUE(v)) {
28642923
/* Fast path; single digit long (31 bits) will cast safely
28652924
to double. This improves performance of FP/long operations
28662925
by 20%.
28672926
*/
2868-
return (double)MEDIUM_VALUE((PyLongObject *)v);
2927+
return (double)medium_value((PyLongObject *)v);
28692928
}
28702929
x = _PyLong_Frexp((PyLongObject *)v, &exponent);
28712930
if ((x == -1.0 && PyErr_Occurred()) || exponent > DBL_MAX_EXP) {
@@ -3067,8 +3126,8 @@ long_add(PyLongObject *a, PyLongObject *b)
30673126

30683127
CHECK_BINOP(a, b);
30693128

3070-
if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
3071-
return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b));
3129+
if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b)) {
3130+
return _PyLong_FromSTwoDigits(medium_value(a) + medium_value(b));
30723131
}
30733132
if (Py_SIZE(a) < 0) {
30743133
if (Py_SIZE(b) < 0) {
@@ -3101,8 +3160,8 @@ long_sub(PyLongObject *a, PyLongObject *b)
31013160

31023161
CHECK_BINOP(a, b);
31033162

3104-
if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
3105-
return PyLong_FromLong(MEDIUM_VALUE(a) - MEDIUM_VALUE(b));
3163+
if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b)) {
3164+
return _PyLong_FromSTwoDigits(medium_value(a) - medium_value(b));
31063165
}
31073166
if (Py_SIZE(a) < 0) {
31083167
if (Py_SIZE(b) < 0) {
@@ -3536,9 +3595,9 @@ long_mul(PyLongObject *a, PyLongObject *b)
35363595
CHECK_BINOP(a, b);
35373596

35383597
/* fast path for single-digit multiplication */
3539-
if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
3540-
stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);
3541-
return PyLong_FromLongLong((long long)v);
3598+
if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b)) {
3599+
stwodigits v = medium_value(a) * medium_value(b);
3600+
return _PyLong_FromSTwoDigits(v);
35423601
}
35433602

35443603
z = k_mul(a, b);
@@ -4343,8 +4402,8 @@ long_invert(PyLongObject *v)
43434402
{
43444403
/* Implement ~x as -(x+1) */
43454404
PyLongObject *x;
4346-
if (Py_ABS(Py_SIZE(v)) <=1)
4347-
return PyLong_FromLong(-(MEDIUM_VALUE(v)+1));
4405+
if (IS_MEDIUM_VALUE(v))
4406+
return _PyLong_FromSTwoDigits(~medium_value(v));
43484407
x = (PyLongObject *) long_add(v, (PyLongObject *)_PyLong_GetOne());
43494408
if (x == NULL)
43504409
return NULL;
@@ -4358,8 +4417,8 @@ static PyObject *
43584417
long_neg(PyLongObject *v)
43594418
{
43604419
PyLongObject *z;
4361-
if (Py_ABS(Py_SIZE(v)) <= 1)
4362-
return PyLong_FromLong(-MEDIUM_VALUE(v));
4420+
if (IS_MEDIUM_VALUE(v))
4421+
return _PyLong_FromSTwoDigits(-medium_value(v));
43634422
z = (PyLongObject *)_PyLong_Copy(v);
43644423
if (z != NULL)
43654424
Py_SET_SIZE(z, -(Py_SIZE(v)));
@@ -4704,28 +4763,37 @@ long_bitwise(PyLongObject *a,
47044763
static PyObject *
47054764
long_and(PyObject *a, PyObject *b)
47064765
{
4707-
PyObject *c;
47084766
CHECK_BINOP(a, b);
4709-
c = long_bitwise((PyLongObject*)a, '&', (PyLongObject*)b);
4710-
return c;
4767+
PyLongObject *x = (PyLongObject*)a;
4768+
PyLongObject *y = (PyLongObject*)b;
4769+
if (IS_MEDIUM_VALUE(x) && IS_MEDIUM_VALUE(y)) {
4770+
return _PyLong_FromSTwoDigits(medium_value(x) & medium_value(y));
4771+
}
4772+
return long_bitwise(x, '&', y);
47114773
}
47124774

47134775
static PyObject *
47144776
long_xor(PyObject *a, PyObject *b)
47154777
{
4716-
PyObject *c;
47174778
CHECK_BINOP(a, b);
4718-
c = long_bitwise((PyLongObject*)a, '^', (PyLongObject*)b);
4719-
return c;
4779+
PyLongObject *x = (PyLongObject*)a;
4780+
PyLongObject *y = (PyLongObject*)b;
4781+
if (IS_MEDIUM_VALUE(x) && IS_MEDIUM_VALUE(y)) {
4782+
return _PyLong_FromSTwoDigits(medium_value(x) ^ medium_value(y));
4783+
}
4784+
return long_bitwise(x, '^', y);
47204785
}
47214786

47224787
static PyObject *
47234788
long_or(PyObject *a, PyObject *b)
47244789
{
4725-
PyObject *c;
47264790
CHECK_BINOP(a, b);
4727-
c = long_bitwise((PyLongObject*)a, '|', (PyLongObject*)b);
4728-
return c;
4791+
PyLongObject *x = (PyLongObject*)a;
4792+
PyLongObject *y = (PyLongObject*)b;
4793+
if (IS_MEDIUM_VALUE(x) && IS_MEDIUM_VALUE(y)) {
4794+
return _PyLong_FromSTwoDigits(medium_value(x) | medium_value(y));
4795+
}
4796+
return long_bitwise(x, '|', y);
47294797
}
47304798

47314799
static PyObject *

0 commit comments

Comments
 (0)