https://github.com/python/cpython/commit/c9d123482acebcf725c847bd6f8354bdd9314189
commit: c9d123482acebcf725c847bd6f8354bdd9314189
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-03-03T12:15:32+01:00
summary:

gh-144995: Optimize memoryview == memoryview (#144996)

Optimize memoryview comparison: a memoryview is equal to itself, there is no
need to compare values, except if it uses float format.

Benchmark comparing 1 MiB:

    from timeit import timeit
    with open("/dev/random", 'br') as fp:
        data = fp.read(2**20)
    view = memoryview(data)
    LOOPS = 1_000
    b = timeit('x == x', number=LOOPS, globals={'x': data})
    m = timeit('x == x', number=LOOPS, globals={'x': view})
    print("bytes %f seconds" % b)
    print("mview %f seconds" % m)
    print("=> %f time slower" % (m / b))

Result before the change:

    bytes 0.000026 seconds
    mview 1.445791 seconds
    => 55660.873940 time slower

Result after the change:

    bytes 0.000026 seconds
    mview 0.000028 seconds
    => 1.104382 time slower

This missed optimization was discovered by Pierre-Yves David
while working on Mercurial.

Co-authored-by: Pieter Eendebak <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst
M Lib/test/test_memoryview.py
M Objects/memoryobject.c

diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index 656318668e6d6e..0f7dc15b8c6f2c 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -575,6 +575,67 @@ 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.
+
+        def check_equal(view, is_equal):
+            self.assertEqual(view == view, is_equal)
+            self.assertEqual(view != view, not is_equal)
+
+            # Comparison with a different memoryview doesn't use
+            # the optimization and should give the same result.
+            view2 = memoryview(view)
+            self.assertEqual(view2 == view, is_equal)
+            self.assertEqual(view2 != view2, not is_equal)
+
+        # Test integer formats
+        for int_format in 'bBhHiIlLqQ':
+            with self.subTest(format=int_format):
+                a = array.array(int_format, [1, 2, 3])
+                m = memoryview(a)
+                check_equal(m, True)
+
+                if int_format in 'bB':
+                    m2 = m.cast('@' + m.format)
+                    check_equal(m2, True)
+
+        # Test 'c' format
+        a = array.array('B', [1, 2, 3])
+        m = memoryview(a.tobytes()).cast('c')
+        check_equal(m, True)
+
+        # Test 'n' and 'N' formats
+        if struct.calcsize('L') == struct.calcsize('N'):
+            int_format = 'L'
+        elif struct.calcsize('Q') == struct.calcsize('N'):
+            int_format = 'Q'
+        else:
+            int_format = None
+        if int_format:
+            a = array.array(int_format, [1, 2, 3])
+            m = memoryview(a.tobytes()).cast('N')
+            check_equal(m, True)
+            m = memoryview(a.tobytes()).cast('n')
+            check_equal(m, True)
+
+        # Test '?' format
+        m = memoryview(b'\0\1\2').cast('?')
+        check_equal(m, True)
+
+        # Test float formats
+        for float_format in 'fd':
+            with self.subTest(format=float_format):
+                a = array.array(float_format, [1.0, 2.0, float('nan')])
+                m = memoryview(a)
+                # nan is not equal to nan
+                check_equal(m, False)
+
+                a = array.array(float_format, [1.0, 2.0, 3.0])
+                m = memoryview(a)
+                check_equal(m, True)
+
 
 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..0ad4f02d80bf50 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -3122,6 +3122,30 @@ memory_richcompare(PyObject *v, PyObject *w, int op)
     }
     vv = VIEW_ADDR(v);
 
+    // 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) {
+        const char *format = vv->format;
+        if (format != NULL) {
+            if (*format == '@') {
+                format++;
+            }
+            // Include only formats known by struct, exclude float formats
+            // "d" (double), "f" (float) and "e" (16-bit float).
+            // Do not optimize "P" format.
+            if (format[0] != 0
+                && strchr("bBchHiIlLnNqQ?", format[0]) != NULL
+                && format[1] == 0)
+            {
+                equal = 1;
+                goto result;
+            }
+        }
+    }
+
     if (PyMemoryView_Check(w)) {
         if (BASE_INACCESSIBLE(w)) {
             equal = (v == w);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to