Skip to content

Commit 0b26e33

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 0b26e33

3 files changed

Lines changed: 46 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: 35 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,42 @@ list_length(PyObject *a)
707708
return PyList_GET_SIZE(a);
708709
}
709710

711+
static inline int
712+
list_item_eq(PyObject *item, PyObject *value, PyTypeObject *value_type)
713+
{
714+
/* Identity implies equality, as in PyObject_RichCompareBool(). */
715+
if (item == value) {
716+
return 1;
717+
}
718+
if (Py_TYPE(item) == value_type) {
719+
if (value_type == &PyLong_Type) {
720+
PyLongObject *v = (PyLongObject *)item;
721+
PyLongObject *w = (PyLongObject *)value;
722+
if (_PyLong_BothAreCompact(v, w)) {
723+
return _PyLong_CompactValue(v) == _PyLong_CompactValue(w);
724+
}
725+
}
726+
else if (value_type == &PyFloat_Type) {
727+
return PyFloat_AS_DOUBLE(item) == PyFloat_AS_DOUBLE(value);
728+
}
729+
else if (value_type == &PyUnicode_Type) {
730+
return _PyUnicode_Equal(item, value);
731+
}
732+
}
733+
return PyObject_RichCompareBool(item, value, Py_EQ);
734+
}
735+
710736
static int
711737
list_contains(PyObject *aa, PyObject *el)
712738
{
713-
739+
PyTypeObject *el_type = (el != NULL) ? Py_TYPE(el) : NULL;
714740
for (Py_ssize_t i = 0; ; i++) {
715741
PyObject *item = list_get_item_ref((PyListObject *)aa, i);
716742
if (item == NULL) {
717743
// out-of-bounds
718744
return 0;
719745
}
720-
int cmp = PyObject_RichCompareBool(item, el, Py_EQ);
746+
int cmp = list_item_eq(item, el, el_type);
721747
Py_DECREF(item);
722748
if (cmp != 0) {
723749
return cmp;
@@ -3391,13 +3417,14 @@ list_index_impl(PyListObject *self, PyObject *value, Py_ssize_t start,
33913417
if (stop < 0)
33923418
stop = 0;
33933419
}
3420+
PyTypeObject *value_type = Py_TYPE(value);
33943421
for (Py_ssize_t i = start; i < stop; i++) {
33953422
PyObject *obj = list_get_item_ref(self, i);
33963423
if (obj == NULL) {
33973424
// out-of-bounds
33983425
break;
33993426
}
3400-
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
3427+
int cmp = list_item_eq(obj, value, value_type);
34013428
Py_DECREF(obj);
34023429
if (cmp > 0)
34033430
return PyLong_FromSsize_t(i);
@@ -3422,6 +3449,7 @@ list_count_impl(PyListObject *self, PyObject *value)
34223449
/*[clinic end generated code: output=eff66f14aef2df86 input=3bdc3a5e6f749565]*/
34233450
{
34243451
Py_ssize_t count = 0;
3452+
PyTypeObject *value_type = Py_TYPE(value);
34253453
for (Py_ssize_t i = 0; ; i++) {
34263454
PyObject *obj = list_get_item_ref(self, i);
34273455
if (obj == NULL) {
@@ -3433,7 +3461,7 @@ list_count_impl(PyListObject *self, PyObject *value)
34333461
Py_DECREF(obj);
34343462
continue;
34353463
}
3436-
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
3464+
int cmp = list_item_eq(obj, value, value_type);
34373465
Py_DECREF(obj);
34383466
if (cmp > 0)
34393467
count++;
@@ -3460,11 +3488,12 @@ list_remove_impl(PyListObject *self, PyObject *value)
34603488
/*[clinic end generated code: output=b9b76a6633b18778 input=26c813dbb95aa93b]*/
34613489
{
34623490
Py_ssize_t i;
3491+
PyTypeObject *value_type = Py_TYPE(value);
34633492

34643493
for (i = 0; i < Py_SIZE(self); i++) {
34653494
PyObject *obj = self->ob_item[i];
34663495
Py_INCREF(obj);
3467-
int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
3496+
int cmp = list_item_eq(obj, value, value_type);
34683497
Py_DECREF(obj);
34693498
if (cmp > 0) {
34703499
if (list_ass_slice_lock_held(self, i, i+1, NULL) == 0)
@@ -3522,7 +3551,7 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op)
35223551

35233552
Py_INCREF(vitem);
35243553
Py_INCREF(witem);
3525-
int k = PyObject_RichCompareBool(vitem, witem, Py_EQ);
3554+
int k = list_item_eq(vitem, witem, Py_TYPE(witem));
35263555
if (k < 0) {
35273556
Py_DECREF(vitem);
35283557
Py_DECREF(witem);

0 commit comments

Comments
 (0)