Skip to content

Commit 8dfd0f0

Browse files
committed
gh-159092: Optimize comparisons in list operations
Use specialized comparisons for exact int, float and str objects to avoid unnecessary rich-comparison dispatch in list operations.
1 parent 5a22a62 commit 8dfd0f0

3 files changed

Lines changed: 59 additions & 6 deletions

File tree

‎Lib/test/test_list.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,16 @@ def __eq__(self, other):
363363
lst = [X(), X()]
364364
X() in lst
365365

366+
def test_eq_identity_shortcut(self):
367+
# PyObject_RichCompareBool() treats identity as equality, even for
368+
# objects that are not equal to themselves (see seq_tests).
369+
nan = float("nan")
370+
self.assertIn(nan, [nan])
371+
self.assertEqual([nan].index(nan), 0)
372+
self.assertEqual([nan].count(nan), 1)
373+
self.assertEqual([nan], [nan])
374+
self.assertNotEqual([nan], [float("nan")])
375+
366376
def test_tier2_invalidates_iterator(self):
367377
# GH-121012
368378
for _ in range(100):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up ``x in list``, :meth:`list.index`, :meth:`list.count`, :meth:`list.remove` and equality comparisons for lists of integers, floats and strings by specializing the per-item equality check.

‎Objects/listobject.c‎

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "pycore_stackref.h" // _Py_TryIncrefCompareStackRef()
1717
#include "pycore_tuple.h" // _PyTuple_FromArraySteal()
1818
#include "pycore_typeobject.h" // _Py_TYPE_VERSION_LIST
19+
#include "pycore_unicodeobject.h" // _PyUnicode_Equal()
1920
#include <stddef.h>
2021

2122
/*[clinic input]
@@ -707,17 +708,55 @@ list_length(PyObject *a)
707708
return PyList_GET_SIZE(a);
708709
}
709710

711+
static inline PyTypeObject *
712+
list_fast_eq_type(PyObject *value)
713+
{
714+
if (value != NULL) {
715+
PyTypeObject *type = Py_TYPE(value);
716+
if (type == &PyLong_Type || type == &PyFloat_Type ||
717+
type == &PyUnicode_Type) {
718+
return type;
719+
}
720+
}
721+
return NULL;
722+
}
723+
724+
static inline int
725+
list_item_eq(PyObject *item, PyObject *value, PyTypeObject *fast_type)
726+
{
727+
if (fast_type != NULL && Py_TYPE(item) == fast_type) {
728+
if (item == value) {
729+
return 1;
730+
}
731+
if (fast_type == &PyLong_Type) {
732+
PyLongObject *v = (PyLongObject *)item;
733+
PyLongObject *w = (PyLongObject *)value;
734+
if (_PyLong_BothAreCompact(v, w)) {
735+
return _PyLong_CompactValue(v) == _PyLong_CompactValue(w);
736+
}
737+
}
738+
else if (fast_type == &PyFloat_Type) {
739+
return PyFloat_AS_DOUBLE(item) == PyFloat_AS_DOUBLE(value);
740+
}
741+
else {
742+
assert(fast_type == &PyUnicode_Type);
743+
return _PyUnicode_Equal(item, value);
744+
}
745+
}
746+
return PyObject_RichCompareBool(item, value, Py_EQ);
747+
}
748+
710749
static int
711750
list_contains(PyObject *aa, PyObject *el)
712751
{
713-
752+
PyTypeObject *fast_type = list_fast_eq_type(el);
714753
for (Py_ssize_t i = 0; ; i++) {
715754
PyObject *item = list_get_item_ref((PyListObject *)aa, i);
716755
if (item == NULL) {
717756
// out-of-bounds
718757
return 0;
719758
}
720-
int cmp = PyObject_RichCompareBool(item, el, Py_EQ);
759+
int cmp = list_item_eq(item, el, fast_type);
721760
Py_DECREF(item);
722761
if (cmp != 0) {
723762
return cmp;
@@ -3391,13 +3430,14 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start,
33913430
if (stop < 0)
33923431
stop = 0;
33933432
}
3433+
PyTypeObject *fast_type = list_fast_eq_type(value);
33943434
for (Py_ssize_t i = start; i < stop; i++) {
33953435
PyObject *obj = list_get_item_ref(self, i);
33963436
if (obj == NULL) {
33973437
// out-of-bounds
33983438
break;
33993439
}
3400-
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
3440+
int cmp = list_item_eq(obj, value, fast_type);
34013441
Py_DECREF(obj);
34023442
if (cmp > 0)
34033443
return PyLong_FromSsize_t(i);
@@ -3422,6 +3462,7 @@ list_count_impl(PyListObject *self, PyObject *value)
34223462
/*[clinic end generated code: output=eff66f14aef2df86 input=3bdc3a5e6f749565]*/
34233463
{
34243464
Py_ssize_t count = 0;
3465+
PyTypeObject *fast_type = list_fast_eq_type(value);
34253466
for (Py_ssize_t i = 0; ; i++) {
34263467
PyObject *obj = list_get_item_ref(self, i);
34273468
if (obj == NULL) {
@@ -3433,7 +3474,7 @@ list_count_impl(PyListObject *self, PyObject *value)
34333474
Py_DECREF(obj);
34343475
continue;
34353476
}
3436-
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
3477+
int cmp = list_item_eq(obj, value, fast_type);
34373478
Py_DECREF(obj);
34383479
if (cmp > 0)
34393480
count++;
@@ -3460,11 +3501,12 @@ list_remove_impl(PyListObject *self, PyObject *value)
34603501
/*[clinic end generated code: output=b9b76a6633b18778 input=26c813dbb95aa93b]*/
34613502
{
34623503
Py_ssize_t i;
3504+
PyTypeObject *fast_type = list_fast_eq_type(value);
34633505

34643506
for (i = 0; i < Py_SIZE(self); i++) {
34653507
PyObject *obj = self->ob_item[i];
34663508
Py_INCREF(obj);
3467-
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
3509+
int cmp = list_item_eq(obj, value, fast_type);
34683510
Py_DECREF(obj);
34693511
if (cmp > 0) {
34703512
if (list_ass_slice_lock_held(self, i, i+1, NULL) == 0)
@@ -3522,7 +3564,7 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op)
35223564

35233565
Py_INCREF(vitem);
35243566
Py_INCREF(witem);
3525-
int k = PyObject_RichCompareBool(vitem, witem, Py_EQ);
3567+
int k = list_item_eq(vitem, witem, list_fast_eq_type(witem));
35263568
if (k < 0) {
35273569
Py_DECREF(vitem);
35283570
Py_DECREF(witem);

0 commit comments

Comments
 (0)