Skip to content

Optimize HashSink and _MD5Sink.updateHash (unroll 4 rounds) - #1014

Closed
jonasfj wants to merge 5 commits into
dart-lang:mainfrom
jonasfj:crypto-md5-round4
Closed

jonasfj wants to merge 5 commits into
dart-lang:mainfrom
jonasfj:crypto-md5-round4

Conversation

@jonasfj

@jonasfj jonasfj commented Oct 14, 2026

Copy link
Copy Markdown
Member

Optimize HashSink and _MD5Sink.updateHash to reduce allocations and improve MD5 throughput (~1.4x in AOT, ~1.25x in JIT):

4 commits:

  • Make HashSink extend ByteConversionSink directly:
    Eliminates the _ByteAdapterSink wrapper allocation and virtual Sink<List<int>> dispatch in startChunkedConversion, and overrides addSlice to avoid sublist allocations.

  • Share buffer between _chunk and _chunk32 in HashSink:
    Uses a single Uint32List(chunkSizeInWords) buffer shared by _chunk and _byteDataView, and skips the 16-word getUint32 copy loop per block when _endian == Endian.host.

  • Pad in-place in HashSink._finalizeAndProcessData and use setUint64:
    Writes the 0x80 marker, zero padding, and 64-bit bit-length directly into _chunk without allocating a temporary padding Uint8List, and uses _byteDataView.setUint64 when !const bool.fromEnvironment('dart.library.js_interop').

  • Unroll _MD5Sink.updateHash rounds by 4:
    Replaces the nested round(int i) closure and _shiftAmounts lookup table with static @pragma('vm:prefer-inline') round helpers (_ff, _gg, _hh, _ii) and constant shift amounts.

Make HashSink extend ByteConversionSink instead of implementing
Sink<List<int>> and wrapping it with ByteConversionSink.from(...) in
each Hash.startChunkedConversion implementation.

Also override HashSink.addSlice to pass (data, start, end) directly to
_addData without allocating a sublist copy.
…HashSink

Allocate a single Uint32List(chunkSizeInWords) buffer in HashSink and
create _chunk (asUint8List) and non-nullable _byteDataView (asByteData)
as views over the same ByteBuffer.

Once a chunk is filled, each 32-bit word j is read from _byteDataView
at [j * 4 .. j * 4 + 3] and written in-place to _chunk32[j] at the same
4-byte offset before calling updateHash(_chunk32), after which
chunkNextIndex is reset to 0.
…an.host

Because _chunk and _chunk32 are views over the same ByteBuffer and
Uint32List accesses words in Endian.host byte order, _chunk32[j]
already holds _byteDataView.getUint32(j * 4, _endian) whenever
_endian == Endian.host.
Pass constant shift amounts directly to round(e, f, i, s) in each of the
four 16-step loops so rotl32 compiles to immediate 32-bit rotates, and
replace % 16 with & 15.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant