Skip to content

[dart:typed_data] Add native memory range comparison API (memcmp / memoryEqualsRange) for TypedData #64095

Closed

Description

@sigurdm

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:

  1. Allocating view wrapper objects (e.g. buffer.asUint8List(offset, length)) combined with element-by-element loops from package:collection (ListEquality).
  2. Writing manual scalar Dart for loops 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 $O(N)$ costs.

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

  1. Compile-time Type Safety: uint32ListA.memoryEqualsRange(uint8ListB) is caught at compile time because T must match.
  2. Clear Naming: memoryEqualsRange explicitly indicates that comparison is based on raw memory representation (e.g. exact bit patterns), distinct from custom or IEEE 754 float equality.
  3. Element-scaled indexing: Offsets and lengths are in element units of T, preventing offset-multiplication errors, while the VM lowers the call to memcmp with length * 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

  1. kevmoo commented on Aug 21, 2026

    @kevmoo
    Member

    LOVE IT! Yes please!

  2. lrhn commented on Aug 21, 2026

    @lrhn
    Member

    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 T can be bound to fx TypedDataList<int>.

    I'd put a rangeEquals(int start, int end, SelfType list, [int offset = 0]) and similar compareRange on each type individually.
    Or just not require the same type.

    And I'd not say memory in 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" memcmp only 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), or bytesEqual(int start, int end, TypedData other, [int offset = 0]), or maybe even have it defined on ByteBuffer.

    Or only have the functionality on Uint8List and ByteData.

  3. added
    area-core-librarySDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries.
    type-enhancementA request for a change that isn't a bug
    on Aug 21, 2026
  4. mraleph commented on Aug 21, 2026

    @mraleph
    Member

    FWIW for large enough inputs you can just call memcmp, e.g. something like:

    import 'dart:ffi';
    
    @Native<Int Function(Pointer<Uint8> a, Pointer<Uint8> b, Size sizeInBytes)>(symbol: 'memcmp', isLeaf: true)
    int _memcmp(Pointer<Uint8> a, Pointer<Uint8> b, int sizeInBytes);
    
    bool bytesEqual(Uint8List a, Uint8List b) {
      return a.length == b.length && _memcmp(a.address, b.address, a.length);
    }
  5. sigurdm commented on Aug 28, 2026

    @sigurdm
    ContributorAuthor

    import 'dart:ffi';

    @Native<Int Function(Pointer a, Pointer b, Size sizeInBytes)>(symbol: 'memcmp', isLeaf: true)
    int _memcmp(Pointer a, Pointer b, int sizeInBytes);

    bool bytesEqual(Uint8List a, Uint8List b) {
    return a.length == b.length && _memcmp(a.address, b.address, a.length);
    }

    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.

  6. sigurdm commented on Aug 28, 2026

    @sigurdm
    ContributorAuthor

    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 T can be bound to fx TypedDataList<int>.

    Oh - didn't consider that. Ok - I guess we could put an assert somewhere.

    I'd put a rangeEquals(int start, int end, SelfType list, [int offset = 0]) and similar compareRange on each type individually. Or just not require the same type.

    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

    And I'd not say memory in 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" memcmp only agrees on big endian integers. That means the same code may give different results on different endian machines.

    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.

    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), or bytesEqual(int start, int end, TypedData other, [int offset = 0]), or maybe even have it defined on ByteBuffer.

    Or only have the functionality on Uint8List and ByteData.

    Would also work for me!

  7. modulovalue commented on Sep 11, 2026

    @modulovalue
    Contributor

    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:simd or 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-level memoryEqualsRange-ish API.

    @sigurdm if you have any other SIMD related use cases on your wishlist, feel free to share them with me, see: #64170

  8. sigurdm commented on Sep 14, 2026

    @sigurdm
    ContributorAuthor

    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:simd or 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-level memoryEqualsRange-ish API.

    @sigurdm if you have any other SIMD related use cases on your wishlist, feel free to share them with me, see: #64170

    @modulovalue this proposal is for list-like boxed buffers, not for fixed-size buffers.

  9. modulovalue commented on Sep 14, 2026

    @modulovalue
    Contributor

    @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?

  10. sigurdm commented on Sep 14, 2026

    @sigurdm
    ContributorAuthor

    Oh - we can surely benefit from simd in general. But not from Int32x4.equal.

  11. modulovalue commented on Sep 14, 2026

    @modulovalue
    Contributor

    But not from Int32x4.equal.

    @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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-core-librarySDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries.library-typed-datatype-enhancementA request for a change that isn't a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions