Skip to content

Commit 7c17e23

Browse files
adrian17pitrou
authored andcommitted
bpo-24700: Add a fast path for comparing array.array of equal type (#3009)
1 parent ee84a60 commit 7c17e23

File tree

3 files changed

+78
-15
lines changed

3 files changed

+78
-15
lines changed

Lib/test/test_array.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,16 @@ class FPTest(NumberTest):
13421342
def assertEntryEqual(self, entry1, entry2):
13431343
self.assertAlmostEqual(entry1, entry2)
13441344

1345+
def test_nan(self):
1346+
a = array.array(self.typecode, [float('nan')])
1347+
b = array.array(self.typecode, [float('nan')])
1348+
self.assertIs(a != b, True)
1349+
self.assertIs(a == b, False)
1350+
self.assertIs(a > b, False)
1351+
self.assertIs(a >= b, False)
1352+
self.assertIs(a < b, False)
1353+
self.assertIs(a <= b, False)
1354+
13451355
def test_byteswap(self):
13461356
a = array.array(self.typecode, self.example)
13471357
self.assertRaises(TypeError, a.byteswap, 42)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Optimize array.array comparison. It is now from 10x up to 70x faster when
2+
comparing arrays holding values of the same integer type.

Modules/arraymodule.c

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct arraydescr {
3131
int itemsize;
3232
PyObject * (*getitem)(struct arrayobject *, Py_ssize_t);
3333
int (*setitem)(struct arrayobject *, Py_ssize_t, PyObject *);
34+
int (*compareitems)(const void *, const void *, Py_ssize_t);
3435
const char *formats;
3536
int is_integer_type;
3637
int is_signed;
@@ -518,26 +519,48 @@ d_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
518519
return 0;
519520
}
520521

522+
#define DEFINE_COMPAREITEMS(code, type) \
523+
static int \
524+
code##_compareitems(const void *lhs, const void *rhs, Py_ssize_t length) \
525+
{ \
526+
const type *a = lhs, *b = rhs; \
527+
for (Py_ssize_t i = 0; i < length; ++i) \
528+
if (a[i] != b[i]) \
529+
return a[i] < b[i] ? -1 : 1; \
530+
return 0; \
531+
}
532+
533+
DEFINE_COMPAREITEMS(b, signed char)
534+
DEFINE_COMPAREITEMS(BB, unsigned char)
535+
DEFINE_COMPAREITEMS(u, Py_UNICODE)
536+
DEFINE_COMPAREITEMS(h, short)
537+
DEFINE_COMPAREITEMS(HH, unsigned short)
538+
DEFINE_COMPAREITEMS(i, int)
539+
DEFINE_COMPAREITEMS(II, unsigned int)
540+
DEFINE_COMPAREITEMS(l, long)
541+
DEFINE_COMPAREITEMS(LL, unsigned long)
542+
DEFINE_COMPAREITEMS(q, long long)
543+
DEFINE_COMPAREITEMS(QQ, unsigned long long)
521544

522545
/* Description of types.
523546
*
524547
* Don't forget to update typecode_to_mformat_code() if you add a new
525548
* typecode.
526549
*/
527550
static const struct arraydescr descriptors[] = {
528-
{'b', 1, b_getitem, b_setitem, "b", 1, 1},
529-
{'B', 1, BB_getitem, BB_setitem, "B", 1, 0},
530-
{'u', sizeof(Py_UNICODE), u_getitem, u_setitem, "u", 0, 0},
531-
{'h', sizeof(short), h_getitem, h_setitem, "h", 1, 1},
532-
{'H', sizeof(short), HH_getitem, HH_setitem, "H", 1, 0},
533-
{'i', sizeof(int), i_getitem, i_setitem, "i", 1, 1},
534-
{'I', sizeof(int), II_getitem, II_setitem, "I", 1, 0},
535-
{'l', sizeof(long), l_getitem, l_setitem, "l", 1, 1},
536-
{'L', sizeof(long), LL_getitem, LL_setitem, "L", 1, 0},
537-
{'q', sizeof(long long), q_getitem, q_setitem, "q", 1, 1},
538-
{'Q', sizeof(long long), QQ_getitem, QQ_setitem, "Q", 1, 0},
539-
{'f', sizeof(float), f_getitem, f_setitem, "f", 0, 0},
540-
{'d', sizeof(double), d_getitem, d_setitem, "d", 0, 0},
551+
{'b', 1, b_getitem, b_setitem, b_compareitems, "b", 1, 1},
552+
{'B', 1, BB_getitem, BB_setitem, BB_compareitems, "B", 1, 0},
553+
{'u', sizeof(Py_UNICODE), u_getitem, u_setitem, u_compareitems, "u", 0, 0},
554+
{'h', sizeof(short), h_getitem, h_setitem, h_compareitems, "h", 1, 1},
555+
{'H', sizeof(short), HH_getitem, HH_setitem, HH_compareitems, "H", 1, 0},
556+
{'i', sizeof(int), i_getitem, i_setitem, i_compareitems, "i", 1, 1},
557+
{'I', sizeof(int), II_getitem, II_setitem, II_compareitems, "I", 1, 0},
558+
{'l', sizeof(long), l_getitem, l_setitem, l_compareitems, "l", 1, 1},
559+
{'L', sizeof(long), LL_getitem, LL_setitem, LL_compareitems, "L", 1, 0},
560+
{'q', sizeof(long long), q_getitem, q_setitem, q_compareitems, "q", 1, 1},
561+
{'Q', sizeof(long long), QQ_getitem, QQ_setitem, QQ_compareitems, "Q", 1, 0},
562+
{'f', sizeof(float), f_getitem, f_setitem, NULL, "f", 0, 0},
563+
{'d', sizeof(double), d_getitem, d_setitem, NULL, "d", 0, 0},
541564
{'\0', 0, 0, 0, 0, 0, 0} /* Sentinel */
542565
};
543566

@@ -664,6 +687,31 @@ array_richcompare(PyObject *v, PyObject *w, int op)
664687
return res;
665688
}
666689

690+
if (va->ob_descr == wa->ob_descr && va->ob_descr->compareitems != NULL) {
691+
/* Fast path:
692+
arrays with same types can have their buffers compared directly */
693+
Py_ssize_t common_length = Py_MIN(Py_SIZE(va), Py_SIZE(wa));
694+
int result = va->ob_descr->compareitems(va->ob_item, wa->ob_item,
695+
common_length);
696+
if (result == 0)
697+
goto compare_sizes;
698+
699+
int cmp;
700+
switch (op) {
701+
case Py_LT: cmp = result < 0; break;
702+
case Py_LE: cmp = result <= 0; break;
703+
case Py_EQ: cmp = result == 0; break;
704+
case Py_NE: cmp = result != 0; break;
705+
case Py_GT: cmp = result > 0; break;
706+
case Py_GE: cmp = result >= 0; break;
707+
default: return NULL; /* cannot happen */
708+
}
709+
PyObject *res = cmp ? Py_True : Py_False;
710+
Py_INCREF(res);
711+
return res;
712+
}
713+
714+
667715
/* Search for the first index where items are different */
668716
k = 1;
669717
for (i = 0; i < Py_SIZE(va) && i < Py_SIZE(wa); i++) {
@@ -685,14 +733,17 @@ array_richcompare(PyObject *v, PyObject *w, int op)
685733

686734
if (k) {
687735
/* No more items to compare -- compare sizes */
736+
compare_sizes: ;
688737
Py_ssize_t vs = Py_SIZE(va);
689738
Py_ssize_t ws = Py_SIZE(wa);
690739
int cmp;
691740
switch (op) {
692741
case Py_LT: cmp = vs < ws; break;
693742
case Py_LE: cmp = vs <= ws; break;
694-
case Py_EQ: cmp = vs == ws; break;
695-
case Py_NE: cmp = vs != ws; break;
743+
/* If the lengths were not equal,
744+
the earlier fast-path check would have caught that. */
745+
case Py_EQ: assert(vs == ws); cmp = 1; break;
746+
case Py_NE: assert(vs == ws); cmp = 0; break;
696747
case Py_GT: cmp = vs > ws; break;
697748
case Py_GE: cmp = vs >= ws; break;
698749
default: return NULL; /* cannot happen */

0 commit comments

Comments
 (0)