From 22945429655ef17821a0f85611e467f87d9c404b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Feb 2026 12:46:53 +0100 Subject: [PATCH 1/4] gh-144995: Optimize memoryview == memoryview --- Lib/test/test_memoryview.py | 21 +++++++++++++++ ...-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst | 2 ++ Objects/memoryobject.c | 27 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 656318668e6d6e..5aa2b0ecd6767d 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -575,6 +575,27 @@ def test_array_assign(self): m[:] = new_a self.assertEqual(a, new_a) + def test_compare_equal(self): + # A memoryview is equal to itself: there is no need to compare + # individual values. This is not true for float values since they can + # be NaN, and NaN is not equal to itself. + for int_format in 'bBhHiIlLqQ': + with self.subTest(format=int_format): + a = array.array(int_format, [1, 2, 3]) + m = memoryview(a) + self.assertTrue(m == m) + + for float_format in 'fd': + with self.subTest(format=int_format): + a = array.array(float_format, [1.0, 2.0, float('nan')]) + m = memoryview(a) + # nan is not equal to nan + self.assertFalse(m == m) + + a = array.array(float_format, [1.0, 2.0, 3.0]) + m = memoryview(a) + self.assertTrue(m == m) + class BytesMemorySliceTest(unittest.TestCase, BaseMemorySliceTests, BaseBytesMemoryTests): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst new file mode 100644 index 00000000000000..83d84b9505c5a5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst @@ -0,0 +1,2 @@ +Optimize :class:`memoryview` comparison: a :class:`memoryview` is equal to +itself, there is no need to compare values. Patch by Victor Stinner. diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index f3b7e4a396b4a1..1f8da86e35f4c1 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3101,6 +3101,25 @@ cmp_rec(const char *p, const char *q, return 1; } +static int +is_float_format(const char *format) +{ + if (format == NULL) { + return 0; + } + if (strcmp("d", format) == 0) { + return 1; + } + if (strcmp("f", format) == 0) { + return 1; + } + if (strcmp("e", format) == 0) { + return 1; + } + return 0; +} + + static PyObject * memory_richcompare(PyObject *v, PyObject *w, int op) { @@ -3122,6 +3141,14 @@ memory_richcompare(PyObject *v, PyObject *w, int op) } vv = VIEW_ADDR(v); + // A memoryview is equal to itself: there is no need to compare individual + // values. This is not true for float values since they can be NaN, and NaN + // is not equal to itself. + if (v == w && !is_float_format(vv->format)) { + equal = 1; + goto result; + } + if (PyMemoryView_Check(w)) { if (BASE_INACCESSIBLE(w)) { equal = (v == w); From 0dd791196b64ec3dde61edbdb15cf4d320519c66 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Feb 2026 16:45:53 +0100 Subject: [PATCH 2/4] Replace blocklist with allowlist --- Objects/memoryobject.c | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 1f8da86e35f4c1..8ba75b51780fff 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3101,25 +3101,6 @@ cmp_rec(const char *p, const char *q, return 1; } -static int -is_float_format(const char *format) -{ - if (format == NULL) { - return 0; - } - if (strcmp("d", format) == 0) { - return 1; - } - if (strcmp("f", format) == 0) { - return 1; - } - if (strcmp("e", format) == 0) { - return 1; - } - return 0; -} - - static PyObject * memory_richcompare(PyObject *v, PyObject *w, int op) { @@ -3143,10 +3124,24 @@ memory_richcompare(PyObject *v, PyObject *w, int op) // A memoryview is equal to itself: there is no need to compare individual // values. This is not true for float values since they can be NaN, and NaN - // is not equal to itself. - if (v == w && !is_float_format(vv->format)) { - equal = 1; - goto result; + // is not equal to itself. So only use this optimization on format known to + // not use floats. + if (v == w) { + int can_compare_ptrs; + const char *format = vv->format; + if (format != NULL) { + // Exclude formats "d" (double), "f" (float), "e" (16-bit float) + // and "P" (void*) + can_compare_ptrs = (strchr("bBchHiIlLnNqQ?", format[0]) != NULL + && format[1] == 0); + } + else { + can_compare_ptrs = 1; + } + if (can_compare_ptrs) { + equal = 1; + goto result; + } } if (PyMemoryView_Check(w)) { From 61e37e485f0846f28b0bcab939aea22b791138e5 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Feb 2026 16:51:48 +0100 Subject: [PATCH 3/4] Dummy change to update GitHub --- Objects/memoryobject.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 8ba75b51780fff..def2f57c14baac 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3127,18 +3127,18 @@ memory_richcompare(PyObject *v, PyObject *w, int op) // is not equal to itself. So only use this optimization on format known to // not use floats. if (v == w) { - int can_compare_ptrs; + int can_compare_ptr; const char *format = vv->format; if (format != NULL) { // Exclude formats "d" (double), "f" (float), "e" (16-bit float) // and "P" (void*) - can_compare_ptrs = (strchr("bBchHiIlLnNqQ?", format[0]) != NULL - && format[1] == 0); + can_compare_ptr = (strchr("bBchHiIlLnNqQ?", format[0]) != NULL + && format[1] == 0); } else { - can_compare_ptrs = 1; + can_compare_ptr = 1; } - if (can_compare_ptrs) { + if (can_compare_ptr) { equal = 1; goto result; } From 102f26d4e963cfd6a06a5ee1a9104ce0446fb9af Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Feb 2026 23:26:09 +0100 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Pieter Eendebak --- Objects/memoryobject.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index def2f57c14baac..76ac3f4d059c60 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3122,16 +3122,17 @@ memory_richcompare(PyObject *v, PyObject *w, int op) } vv = VIEW_ADDR(v); - // A memoryview is equal to itself: there is no need to compare individual - // values. This is not true for float values since they can be NaN, and NaN - // is not equal to itself. So only use this optimization on format known to + // For formats supported by the struct module a memoryview is equal to + // itself: there is no need to compare individual values. + // This is not true for float values since they can be NaN, and NaN + // is not equal to itself. So only use this optimization on format known to // not use floats. if (v == w) { int can_compare_ptr; const char *format = vv->format; if (format != NULL) { - // Exclude formats "d" (double), "f" (float), "e" (16-bit float) - // and "P" (void*) + // Include only formats known by struct, exclude formats "d" (double), + // "f" (float), "e" (16-bit float) and "P" (void*) can_compare_ptr = (strchr("bBchHiIlLnNqQ?", format[0]) != NULL && format[1] == 0); }