Repository navigation
Optimize HashSink and _MD5Sink.updateHash (RFC impl) - #1013
Conversation
c6b25bd to
85261b2
Compare
6e90666 to
1b964a5
Compare
HashSink and _MD5Sink.updateHashHashSink and _MD5Sink.updateHash (RFC impl)
1b964a5 to
a231673
Compare
|
This is a bit faster than #1014, and honestly, easier to review. |
|
I verified the actual rounds against the lines from the RFC: |
77dbd75 to
efe25cc
Compare
Add test/md5_test.dart covering md5 directly (previously md5 was only
exercised indirectly through the RFC 2202 HMAC-MD5 vectors):
- RFC 1321 Appendix A.5 test suite (7 vectors).
- NESSIE test vectors: set 1 ("abcdbcde...nopq", one million "a"),
set 2 (n zero bytes for n = 0..127, i.e. every residue modulo the
64-byte block size through both padding branches), and set 4
(128 zero bits iterated 100000 times).
- md5("a" * n) for n around the 56/64 and 120/128 byte padding
boundaries, with non-zero content so stale buffer bytes would show.
All vectors were cross-checked against Python's hashlib.
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.
efe25cc to
4e94811
Compare
Initialize _byteDataView in the constructor instead of lazily in _addData, so it is non-nullable and the `??=` and `!` are gone from the hot loop. A redirecting constructor is used because the view is created from _chunk, which cannot be referenced while it is itself being initialized in the same initializer list. No behavior change.
Move the endian conversion and updateHash call from the _addData loop into a _processChunk helper, so that a following change can process the final padded chunk without going through _addData. No behavior change.
Write the 0x80 terminator, zero padding and 64-bit message length directly into _chunk and process it with _processChunk, instead of allocating a temporary padding Uint8List and feeding it through _addData. A second chunk is needed exactly when the terminator leaves fewer than _signatureBytes bytes in the current chunk, which is the same condition the previous _roundUp arithmetic expressed. Every byte of the final chunk(s) that is not input data is written explicitly, since _chunk is reused and still holds earlier input.
Write the 64-bit message length with a single setUint64 call, except on web targets where dart2js does not support it and the two setUint32 writes are kept. The check is a const bool.fromEnvironment on dart.library.js_interop (the same condition sha512.dart uses to pick its implementation), so the unused branch is eliminated at compile time.
Translate MD5Transform from RFC 1321 Appendix A.3 (md5c.c) directly using prefer-inline helpers _f/_g/_h/_i and _ff/_gg/_hh/_ii with constant shift amounts (_s11.._s44), constant chunk indices (x[0]..x[15]), and inlined sine table constants, removing the _noise and _shiftAmounts lookup tables. The helpers carry both @pragma('vm:prefer-inline') and @pragma('dart2js:prefer-inline'); dart2js ignores the former, and without inlining the 64 helper calls per block make md5 slower than the previous loop on dart2js.
4e94811 to
4d2b813
Compare
sigurdm
left a comment
There was a problem hiding this comment.
LGTM with a couple of minor comments!
Also, the PR description still reflects the earlier 4-commit version of the branch (in particular the second bullet: "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", which was replaced by making _byteDataView a final field, and the new test/md5_test.dart commit isn't mentioned). Could you update the PR description before squash-merging so the commit message matches the landed code?
|
Gemini has manually checked all 64 MD5 round calls in |
|
As a follow-up idea: the other hashes already inherit the
|
Since _chunk is reused, the fillRange up to _chunk.length - 8 is what zeroes the high 64 bits of a 128-bit length field; say so in the comment instead of "skip ahead 8". Also use _byteDataView directly instead of a local.
…, web Revisions updated by `dart tools/rev_sdk_deps.dart`. core (https://github.com/dart-lang/core/compare/4d80610..72e71ca): 72e71ca0 Mon Oct 19 21:57:11 2026 +0200 Jonas Finnemann Jensen Optimize `HashSink` and `_MD5Sink.updateHash` (RFC impl) (dart-lang/core#1013) dartdoc (https://github.com/dart-lang/dartdoc/compare/e1859cf..c0e19cf): c0e19cf6 Mon Oct 19 11:41:42 2026 -0700 Konstantin Scheglov Import analyzer ast with explicit show. (dart-lang/dartdoc#4300) ecosystem (https://github.com/dart-lang/ecosystem/compare/622ef61..0ab0cfd): 0ab0cfd Mon Oct 19 15:44:26 2026 +0200 Moritz fix(firehose): bump dependency_validator to support analyzer 14 (dart-lang/ecosystem#453) 44a0768 Wed Oct 14 09:37:29 2026 -0700 Nate Bosch Improve firehose publish validation log output (dart-lang/ecosystem#468) http (https://github.com/dart-lang/http/compare/7d7aae2..a8a24a0): a8a24a0 Mon Oct 19 13:31:34 2026 -0700 Kevin Moore docs(web_socket): remove non-user-visible changelog entry (dart-lang/http#2023) 6f6ac5f Mon Oct 19 16:29:43 2026 -0400 Mouad Debbar [http_multi_server] Retry `loopback(0)` when IPv6 port is already bound (dart-lang/http#2025) 6e91ce3 Tue Oct 13 16:25:39 2026 -0700 Kevin Moore Sync root README.md package table from pubspec.yaml files (dart-lang/http#2024) native (https://github.com/dart-lang/native/compare/cc7e4b0..3cbe319): 3cbe319e3 Tue Oct 13 15:47:18 2026 +0200 Daco Harkes [hooks_runner] Fail eagerly when file locking is not supported on file system (dart-lang/native#3679) shelf (https://github.com/dart-lang/shelf/compare/b2cb503..b66665b): b66665b Fri Oct 16 15:12:05 2026 -0700 Kevin Moore chore: add health workflow and prepare shelf 1.4.3, shelf_router 1.1.5, and shelf_static 1.2.0 (dart-lang/shelf#546) test (https://github.com/dart-lang/test/compare/9bf69a8..d1afeb1): d1afeb14 Fri Oct 16 20:41:51 2026 -0400 Mouad Debbar Confirmation that `tearDown` is called on timeout (dart-lang/test#2798) 3f6c2f14 Thu Oct 15 18:21:35 2026 -0700 Jacob MacDonald Enable asserts in the cli compiler (dart-lang/test#2795) tools (https://github.com/dart-lang/tools/compare/c1471db..f6c7a2d): f6c7a2d62 Mon Oct 19 07:59:24 2026 -0700 Kevin Moore [pubspec_parse] Preserve input maps and YamlMap source spans (dart-lang/tools#2653) f1a55d953 Mon Oct 19 07:53:51 2026 -0700 Kevin Moore [yaml] Fix tag URI decoding, numeric tag RangeErrors, and dead checks (dart-lang/tools#2630) 340ded284 Mon Oct 19 07:43:31 2026 -0700 Kevin Moore [unified_analytics] Prepare for 8.2.0 release (dart-lang/tools#2689) a73703543 Fri Oct 16 11:55:03 2026 -0700 Kevin Moore [unified_analytics] Bump version to 8.2.0-wip (dart-lang/tools#2688) web (https://github.com/dart-lang/web/compare/63e6662..4e60e61): 4e60e61 Mon Oct 19 10:02:22 2026 -0700 dependabot[bot] Bump source-map-js from 1.2.1 to 1.2.2 in /web_generator (dart-lang/web#591) 7d63637 Mon Oct 19 09:50:26 2026 -0700 Kevin Moore Add an api.txt golden for package:web (dart-lang/web#590) [email protected] Change-Id: Ibe6ac8a8246a27caa8a4846ea1acc78c801ee2ae Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/566781 Commit-Queue: Nate Bosch <[email protected]> Reviewed-by: Brian Quinlan <[email protected]>
[Optimize
HashSinkand_MD5Sink.updateHashto reduce allocations and improve MD5 throughput (~1.4x in AOT, ~1.25x in JIT):4 commits:
Make
HashSinkextendByteConversionSinkdirectly:Eliminates the
_ByteAdapterSinkwrapper allocation and virtualSink<List<int>>dispatch instartChunkedConversion, and overridesaddSliceto avoidsublistallocations.Share buffer between
_chunkand_chunk32inHashSink:Uses a single
Uint32List(chunkSizeInWords)buffer shared by_chunkand_byteDataView, and skips the 16-wordgetUint32copy loop per block when_endian == Endian.host.Pad in-place in
HashSink._finalizeAndProcessDataand usesetUint64:Writes the
0x80marker, zero padding, and 64-bit bit-length directly into_chunkwithout allocating a temporarypaddingUint8List, and uses_byteDataView.setUint64when!const bool.fromEnvironment('dart.library.js_interop').Implement
_MD5Sink.updateHashfollowing RFC 1321:Translates
MD5Transformfrom RFC 1321 Appendix A.3 directly using@pragma('vm:prefer-inline')helpers (_f/_g/_h/_iand_ff/_gg/_hh/_ii) with constant shift amounts, constant chunk indices, and inlined sine table constants, removing the_noiseand_shiftAmountslookup tables.](https://dart-review.googlesource.com/c/sdk/+/566323/2..4)