Skip to content

Commit 9e35d05

Browse files
authored
gh-96538: Move some type-checking out of bisect.bisect() loops (GH-96539)
1 parent a0ad63e commit 9e35d05

File tree

2 files changed

+143
-16
lines changed

2 files changed

+143
-16
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up ``bisect.bisect()`` functions by taking advantage of type-stability.

Modules/_bisectmodule.c

Lines changed: 142 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ get_bisect_state(PyObject *module)
2525
return (bisect_state *)state;
2626
}
2727

28+
static ssizeargfunc
29+
get_sq_item(PyObject *s)
30+
{
31+
// The parts of PySequence_GetItem that we only need to do once
32+
PyTypeObject *tp = Py_TYPE(s);
33+
PySequenceMethods *m = tp->tp_as_sequence;
34+
if (m && m->sq_item) {
35+
return m->sq_item;
36+
}
37+
const char *msg;
38+
if (tp->tp_as_mapping && tp->tp_as_mapping->mp_subscript) {
39+
msg = "%.200s is not a sequence";
40+
}
41+
else {
42+
msg = "'%.200s' object does not support indexing";
43+
}
44+
PyErr_Format(PyExc_TypeError, msg, tp->tp_name);
45+
return NULL;
46+
}
47+
2848
static inline Py_ssize_t
2949
internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t hi,
3050
PyObject* key)
@@ -42,32 +62,85 @@ internal_bisect_right(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t
4262
if (hi < 0)
4363
return -1;
4464
}
65+
ssizeargfunc sq_item = get_sq_item(list);
66+
if (sq_item == NULL) {
67+
return -1;
68+
}
69+
if (Py_EnterRecursiveCall("in _bisect.bisect_right") < 0) {
70+
return -1;
71+
}
72+
PyTypeObject *tp = Py_TYPE(item);
73+
richcmpfunc compare = tp->tp_richcompare;
4574
while (lo < hi) {
4675
/* The (size_t)cast ensures that the addition and subsequent division
4776
are performed as unsigned operations, avoiding difficulties from
4877
signed overflow. (See issue 13496.) */
4978
mid = ((size_t)lo + hi) / 2;
50-
litem = PySequence_GetItem(list, mid);
51-
if (litem == NULL)
52-
return -1;
79+
assert(mid >= 0);
80+
// PySequence_GetItem, but we already checked the types.
81+
litem = sq_item(list, mid);
82+
assert((PyErr_Occurred() == NULL) ^ (litem == NULL));
83+
if (litem == NULL) {
84+
goto error;
85+
}
5386
if (key != Py_None) {
5487
PyObject *newitem = PyObject_CallOneArg(key, litem);
5588
if (newitem == NULL) {
56-
Py_DECREF(litem);
57-
return -1;
89+
goto error;
5890
}
5991
Py_SETREF(litem, newitem);
6092
}
61-
res = PyObject_RichCompareBool(item, litem, Py_LT);
93+
/* if item < key(list[mid]):
94+
* hi = mid
95+
* else:
96+
* lo = mid + 1
97+
*/
98+
if (compare != NULL && Py_IS_TYPE(litem, tp)) {
99+
// A fast path for comparing objects of the same type
100+
PyObject *res_obj = compare(item, litem, Py_LT);
101+
if (res_obj == Py_True) {
102+
Py_DECREF(res_obj);
103+
Py_DECREF(litem);
104+
hi = mid;
105+
continue;
106+
}
107+
if (res_obj == Py_False) {
108+
Py_DECREF(res_obj);
109+
Py_DECREF(litem);
110+
lo = mid + 1;
111+
continue;
112+
}
113+
if (res_obj == NULL) {
114+
goto error;
115+
}
116+
if (res_obj == Py_NotImplemented) {
117+
Py_DECREF(res_obj);
118+
compare = NULL;
119+
res = PyObject_RichCompareBool(item, litem, Py_LT);
120+
}
121+
else {
122+
res = PyObject_IsTrue(res_obj);
123+
}
124+
}
125+
else {
126+
// A default path for comparing arbitrary objects
127+
res = PyObject_RichCompareBool(item, litem, Py_LT);
128+
}
129+
if (res < 0) {
130+
goto error;
131+
}
62132
Py_DECREF(litem);
63-
if (res < 0)
64-
return -1;
65133
if (res)
66134
hi = mid;
67135
else
68136
lo = mid + 1;
69137
}
138+
Py_LeaveRecursiveCall();
70139
return lo;
140+
error:
141+
Py_LeaveRecursiveCall();
142+
Py_XDECREF(litem);
143+
return -1;
71144
}
72145

73146
/*[clinic input]
@@ -168,32 +241,85 @@ internal_bisect_left(PyObject *list, PyObject *item, Py_ssize_t lo, Py_ssize_t h
168241
if (hi < 0)
169242
return -1;
170243
}
244+
ssizeargfunc sq_item = get_sq_item(list);
245+
if (sq_item == NULL) {
246+
return -1;
247+
}
248+
if (Py_EnterRecursiveCall("in _bisect.bisect_left") < 0) {
249+
return -1;
250+
}
251+
PyTypeObject *tp = Py_TYPE(item);
252+
richcmpfunc compare = tp->tp_richcompare;
171253
while (lo < hi) {
172254
/* The (size_t)cast ensures that the addition and subsequent division
173255
are performed as unsigned operations, avoiding difficulties from
174256
signed overflow. (See issue 13496.) */
175257
mid = ((size_t)lo + hi) / 2;
176-
litem = PySequence_GetItem(list, mid);
177-
if (litem == NULL)
178-
return -1;
258+
assert(mid >= 0);
259+
// PySequence_GetItem, but we already checked the types.
260+
litem = sq_item(list, mid);
261+
assert((PyErr_Occurred() == NULL) ^ (litem == NULL));
262+
if (litem == NULL) {
263+
goto error;
264+
}
179265
if (key != Py_None) {
180266
PyObject *newitem = PyObject_CallOneArg(key, litem);
181267
if (newitem == NULL) {
182-
Py_DECREF(litem);
183-
return -1;
268+
goto error;
184269
}
185270
Py_SETREF(litem, newitem);
186271
}
187-
res = PyObject_RichCompareBool(litem, item, Py_LT);
272+
/* if key(list[mid]) < item:
273+
* lo = mid + 1
274+
* else:
275+
* hi = mid
276+
*/
277+
if (compare != NULL && Py_IS_TYPE(litem, tp)) {
278+
// A fast path for comparing objects of the same type
279+
PyObject *res_obj = compare(litem, item, Py_LT);
280+
if (res_obj == Py_True) {
281+
Py_DECREF(res_obj);
282+
Py_DECREF(litem);
283+
lo = mid + 1;
284+
continue;
285+
}
286+
if (res_obj == Py_False) {
287+
Py_DECREF(res_obj);
288+
Py_DECREF(litem);
289+
hi = mid;
290+
continue;
291+
}
292+
if (res_obj == NULL) {
293+
goto error;
294+
}
295+
if (res_obj == Py_NotImplemented) {
296+
Py_DECREF(res_obj);
297+
compare = NULL;
298+
res = PyObject_RichCompareBool(litem, item, Py_LT);
299+
}
300+
else {
301+
res = PyObject_IsTrue(res_obj);
302+
}
303+
}
304+
else {
305+
// A default path for comparing arbitrary objects
306+
res = PyObject_RichCompareBool(litem, item, Py_LT);
307+
}
308+
if (res < 0) {
309+
goto error;
310+
}
188311
Py_DECREF(litem);
189-
if (res < 0)
190-
return -1;
191312
if (res)
192313
lo = mid + 1;
193314
else
194315
hi = mid;
195316
}
317+
Py_LeaveRecursiveCall();
196318
return lo;
319+
error:
320+
Py_LeaveRecursiveCall();
321+
Py_XDECREF(litem);
322+
return -1;
197323
}
198324

199325

0 commit comments

Comments
 (0)