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]