Repository navigation
[dart:typed_data] Add native memory range comparison API (memcmp / memoryEqualsRange) for TypedData #64095
Description
Summary
While TypedData collections (Uint8List, Uint32List, etc.) correctly use identity-based equality for operator ==, Dart currently lacks an efficient, native/intrinsic way to compare the memory of a subrange of one typed data buffer against another.
Currently, comparing subranges requires either:
- Allocating view wrapper objects (e.g.
buffer.asUint8List(offset, length)) combined with element-by-element loops frompackage:collection(ListEquality). - Writing manual scalar Dart
forloops that compare elements one by one (1, 4, or 8 bytes per iteration) with loop counters, branch overhead, and per-element bounds checking.
Prior Discussions & Context
Earlier issues on the SDK tracker (such as #16335 and #56409) requested overriding operator == and hashCode on TypedData / Uint8List. Those were closed as not planned / intended behavior because mutable collections in Dart adhere to reference identity to preserve hash table invariants and avoid implicit
This proposal does not propose altering operator == or hashCode. Instead, it proposes a dedicated, explicit memory/range comparison method (memoryEqualsRange / memoryCompareRange) that developers can intentionally invoke when bitwise memory comparison is needed, leaving default identity equality untouched.
Performance Benefits of a Native Intrinsic
A native memcmp intrinsic lowered by the VM and Web/Wasm compilers provides substantial performance advantages over userland loops:
- Hardware SIMD Vectorization: Modern runtimes compare 16, 32, or 64 bytes per instruction (using SSE2, AVX2, AVX-512, or ARM NEON).
- Elimination of Branching & Bounds Checks: Replaces hundreds of scalar loop iterations, counter increments, and bounds checks with a single vectorized operation.
- Zero Allocations: Avoids allocating intermediate subview objects on the heap.
Proposed API Shape
A generic extension on T extends TypedData ensures compile-time type safety so different typed arrays cannot be accidentally mixed, while thisOffset, otherOffset, and length are counted in units of elements of T:
extension TypedDataMemoryEquality<T extends TypedData> on T {
/// Returns `true` if the [length] elements in `this` starting at [thisOffset]
/// have identical underlying memory bytes to the elements in [other] starting at [otherOffset].
bool memoryEqualsRange(
T other, {
int thisOffset = 0,
int otherOffset = 0,
int? length,
});
/// Lexicographically compares [length] elements in `this` starting at [thisOffset]
/// against [other] starting at [otherOffset] by their raw memory bytes.
///
/// Returns:
/// - `0` if both ranges contain identical byte sequences.
/// - A negative integer if the range in `this` is lexicographically less than `other`.
/// - A positive integer if the range in `this` is lexicographically greater than `other`.
int memoryCompareRange(
T other, {
int thisOffset = 0,
int otherOffset = 0,
int? length,
});
}Benefits of this API design
- Compile-time Type Safety:
uint32ListA.memoryEqualsRange(uint8ListB)is caught at compile time becauseTmust match. - Clear Naming:
memoryEqualsRangeexplicitly indicates that comparison is based on raw memory representation (e.g. exact bit patterns), distinct from custom or IEEE 754 float equality. - Element-scaled indexing: Offsets and lengths are in element units of
T, preventing offset-multiplication errors, while the VM lowers the call tomemcmpwithlength * elementSizeInBytes.
Use Cases
- Bitmasks and bitsets (e.g. comparing slices of bit vectors in solver and graph algorithms)
- Hashing and cryptography (verifying HMACs, signatures, digest prefixes)
- Serialization and parser token matching (Protobuf, FlatBuffers, packet framing)
- Chunked I/O and streaming data (detecting delimiters and chunk headers without allocating slices)
Activity
kevmoo commentedon Aug 21, 2026
LOVE IT! Yes please!
lrhn commentedon Aug 21, 2026
Functionality would be nice.
If it compares elements of the same type, then it should require the same element type for both arguments.
The extension doesn't guarantee that, since
Tcan be bound to fxTypedDataList<int>.I'd put a
rangeEquals(int start, int end, SelfType list, [int offset = 0])and similarcompareRangeon each type individually.Or just not require the same type.
And I'd not say
memoryin the name. Comparing elements is not the same as comparing bytes. Doesn't matter much for "equals", other than the usual NaN and -0.0, but for a lexical "compare"memcmponly agrees on big endian integers.That means the same code may give different results on different endian machines.
Since it's intended to compare bytes, I'd let it always be byte based: either
bytesEqual(int byteOffset, int byteLengt, TypedData other, int otherByteOffset), orbytesEqual(int start, int end, TypedData other, [int offset = 0]), or maybe even have it defined onByteBuffer.Or only have the functionality on
Uint8ListandByteData.mraleph commentedon Aug 21, 2026
FWIW for large enough inputs you can just call
memcmp, e.g. something like:sigurdm commentedon Aug 28, 2026
Yeah, basically I just want access to memcmp. But I don't think it is fair we have to ask the dart author to do this - this really should be out-of-the-box functionality for typed data IMO.
Also that (probably?) won't work on web.
sigurdm commentedon Aug 28, 2026
Oh - didn't consider that. Ok - I guess we could put an assert somewhere.
That would work!
I think this functionality should be expected to map to memcmp or something equivalent. Therefore I don't think comparing unlike types should be allowed
Hmm, I liked that "memory" implied "close-to-the-iron" I get your point about ordering. Maybe we just scrap or postpone that part of the proposal. Comparing for equality is really what matters most.
Would also work for me!
modulovalue commentedon Sep 11, 2026
Some of my efforts on SIMD make this already possible today @sigurdm! You can use Int32x4.equal from cl/539520 and Int32x4.anyTrue from cl/539400.
The performance is already good on unrolled loops, but will improve significantly soon.
When it comes to usability, I think we should eventually have a
package:simdor something similar with useful, well-tested recipes, but it's still early for that. We are somewhat unique in the language space in this area because we have a great language feature for that: mixins (see: #64235). We will be able to provide convenient abstractions for various use cases. I'd be against an SDK-levelmemoryEqualsRange-ish API.@sigurdm if you have any other SIMD related use cases on your wishlist, feel free to share them with me, see: #64170
sigurdm commentedon Sep 14, 2026
@modulovalue this proposal is for list-like boxed buffers, not for fixed-size buffers.
modulovalue commentedon Sep 14, 2026
@sigurdm given your description I still believe this is solvable with typed_data provided SIMD primitives. However, I could be misunderstanding you. Could you provide a small example program that would benefit from your proposal that in your view could not benefit from SIMD?
sigurdm commentedon Sep 14, 2026
Oh - we can surely benefit from simd in general. But not from Int32x4.equal.
modulovalue commentedon Sep 14, 2026
@sigurdm would appreciate an example as evidence that shows why you think that! (https://en.cppreference.com/cpp/string/byte/memcmp sounds as SIMD as it gets). If it’s about the bit-width (SWAR techniques can be used, but don’t bother), then I’d wait for Int8x16 and others.
[corelib] Add `rangeEquals` to `dart:typed_data`