Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

Commit bf0ff2c

Browse files
authored
Use an MD5 hash instead of Object.hash for model hashes (#147)
We can easily swap out the actual hash algorithm here now, in one place. This is a bit slower (unsurprisingly) than using `Object.hash` etc, a few hundred milliseconds in total per iteration, but is a fairer representation of what we actually want.
1 parent e9b480a commit bf0ff2c

File tree

9 files changed

+150
-109
lines changed

9 files changed

+150
-109
lines changed

pkgs/_macro_host/lib/src/macro_cache.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:crypto/crypto.dart';
56
import 'package:dart_model/dart_model.dart';
67
import 'package:macro_service/macro_service.dart';
78

@@ -32,14 +33,14 @@ class MacroResultsCache {
3233
phase: request.phase,
3334
)] = (
3435
queries: queryResults.map((q) => q.query),
35-
resultsHash:
36+
resultsDigest:
3637
queryResults
3738
.skip(1)
3839
.fold(
3940
queryResults.first.response,
4041
(model, next) => model.mergeWith(next.response),
4142
)
42-
.fingerprint,
43+
.digest,
4344
response: response,
4445
);
4546
}
@@ -67,15 +68,15 @@ class MacroResultsCache {
6768
),
6869
),
6970
);
70-
final newResultsHash =
71+
final newResultsDigest =
7172
queryResults
7273
.skip(1)
7374
.fold(
7475
queryResults.first.model,
7576
(model, next) => model.mergeWith(next.model),
7677
)
77-
.fingerprint;
78-
if (newResultsHash != cached.resultsHash) {
78+
.digest;
79+
if (newResultsDigest != cached.resultsDigest) {
7980
_cache.remove(cacheKey);
8081
return null;
8182
}
@@ -91,8 +92,8 @@ typedef _MacroResultsCacheValue =
9192
/// All queries done by a macro in a given phase.
9293
Iterable<Query> queries,
9394

94-
/// The `identityHash` of the merged model from all query responses.
95-
int resultsHash,
95+
/// The [Digest] of the merged model from all query responses.
96+
Digest resultsDigest,
9697

9798
/// The macro augmentation response that was cached.
9899
AugmentResponse response,

pkgs/dart_model/lib/src/dart_model.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:crypto/crypto.dart';
6+
57
import 'dart_model.g.dart';
68
import 'json_buffer/json_buffer_builder.dart';
79
import 'lazy_merged_map.dart';
@@ -31,10 +33,10 @@ extension ModelExtension on Model {
3133
/// An identity hash for `this`, used for comparing query results.
3234
///
3335
/// TODO: A faster/better implementation?
34-
int get fingerprint {
36+
Digest get digest {
3537
// TODO: Implementation for non-buffer maps?
3638
var node = this.node as MapInBuffer;
37-
return node.buffer.fingerprint(
39+
return node.buffer.digest(
3840
node.pointer,
3941
type: Type.typedMapPointer,
4042
alreadyDereferenced: true,

pkgs/dart_model/lib/src/json_buffer/closed_list.dart

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,11 @@ class _ClosedList with ListMixin<Object?> {
7575
throw UnsupportedError('This JsonBufferBuilder list is read-only.');
7676
}
7777

78-
int get fingerprint {
79-
var iterator = _ClosedListHashIterator(_buffer, _pointer, length);
80-
var hash = 0;
78+
void buildDigest(ByteConversionSink byteSink) {
79+
var iterator = _ClosedListPointerIterator(_buffer, _pointer, length);
8180
while (iterator.moveNext()) {
82-
hash = Object.hash(hash, iterator.current);
81+
_buffer._buildDigest(iterator.current, byteSink);
8382
}
84-
return hash;
8583
}
8684
}
8785

@@ -108,9 +106,9 @@ class _ClosedListIterator<T extends Object?> implements Iterator<T> {
108106
}
109107
}
110108

111-
class _ClosedListHashIterator extends _ClosedListIterator<int> {
112-
_ClosedListHashIterator(super.buffer, super.pointer, super.length);
109+
class _ClosedListPointerIterator extends _ClosedListIterator<_Pointer> {
110+
_ClosedListPointerIterator(super.buffer, super.pointer, super.length);
113111

114112
@override
115-
int get current => _buffer.fingerprint(_pointer);
113+
_Pointer get current => _pointer;
116114
}

pkgs/dart_model/lib/src/json_buffer/closed_map.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,16 @@ class _ClosedMap
123123
@override
124124
int get hashCode => Object.hash(buffer, pointer);
125125

126-
int get fingerprint {
127-
var iterator = _ClosedMapHashIterator(buffer, null, pointer, length);
128-
var hash = 0;
126+
void buildDigest(ByteConversionSink byteSink) {
127+
var iterator = _ClosedMapPointerIterator(buffer, null, pointer, length);
129128
while (iterator.moveNext()) {
130-
hash = Object.hash(hash, iterator.current);
129+
buffer._buildDigest(
130+
iterator.current.$1,
131+
byteSink,
132+
type: Type.stringPointer,
133+
);
134+
buffer._buildDigest(iterator.current.$2, byteSink);
131135
}
132-
return hash;
133136
}
134137
}
135138

@@ -199,17 +202,18 @@ class _ClosedMapEntryIterator
199202
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
200203
}
201204

202-
class _ClosedMapHashIterator extends _ClosedMapIterator<int> {
203-
_ClosedMapHashIterator(
205+
class _ClosedMapPointerIterator
206+
extends _ClosedMapIterator<(int keyPointer, int valuePointer)> {
207+
_ClosedMapPointerIterator(
204208
super._buffer,
205209
super._parent,
206210
super.pointer,
207211
super.length,
208212
);
209213

210214
@override
211-
int get current => Object.hash(
212-
_buffer._fingerprint(_pointer, Type.stringPointer),
213-
_buffer.fingerprint(_pointer + ClosedMaps._keySize),
215+
(int keyPointer, int valuePointer) get current => (
216+
_pointer,
217+
_pointer + ClosedMaps._keySize,
214218
);
215219
}

pkgs/dart_model/lib/src/json_buffer/growable_map.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,16 @@ class _GrowableMap<V>
176176
@override
177177
int get hashCode => Object.hash(buffer, pointer);
178178

179-
int get fingerprint {
180-
var iterator = _GrowableMapHashIterator(buffer, null, pointer);
181-
var hash = 0;
179+
void buildDigest(ByteConversionSink byteSink) {
180+
var iterator = _GrowableMapPointerIterator(buffer, null, pointer);
182181
while (iterator.moveNext()) {
183-
hash = Object.hash(hash, iterator.current);
182+
buffer._buildDigest(
183+
iterator.current.$1,
184+
byteSink,
185+
type: Type.stringPointer,
186+
);
187+
buffer._buildDigest(iterator.current.$2, byteSink);
184188
}
185-
return hash;
186189
}
187190
}
188191

@@ -233,12 +236,13 @@ class _GrowableMapEntryIterator<V>
233236
MapEntry<String, V> get current => MapEntry(_currentKey, _currentValue as V);
234237
}
235238

236-
class _GrowableMapHashIterator extends _GrowableMapIterator<int> {
237-
_GrowableMapHashIterator(super._buffer, super._parent, super._pointer);
239+
class _GrowableMapPointerIterator
240+
extends _GrowableMapIterator<(int keyPointer, int valuePointer)> {
241+
_GrowableMapPointerIterator(super._buffer, super._parent, super._pointer);
238242

239243
@override
240-
int get current => Object.hash(
241-
_buffer._fingerprint(_pointer + _pointerSize, Type.stringPointer),
242-
_buffer.fingerprint(_pointer + _pointerSize + GrowableMaps._keySize),
244+
(int keyPointer, int valuePointer) get current => (
245+
_pointer + _pointerSize,
246+
_pointer + _pointerSize + GrowableMaps._keySize,
243247
);
244248
}

pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import 'dart:collection';
66
import 'dart:convert';
77
import 'dart:typed_data';
88

9+
import 'package:convert/convert.dart';
10+
import 'package:crypto/crypto.dart';
11+
912
part 'closed_list.dart';
1013
part 'closed_map.dart';
1114
part 'explanations.dart';
@@ -62,29 +65,30 @@ class JsonBufferBuilder {
6265
/// If [alreadyDereferenced] is `true`, then for types which are pointers,
6366
/// [pointer] already points at the top of the object, and should not be
6467
/// followed before reading the object.
65-
int fingerprint(int pointer, {Type? type, bool alreadyDereferenced = false}) {
66-
if (type == null) {
67-
type = _readType(pointer);
68-
pointer += _typeSize;
69-
}
70-
return _fingerprint(
68+
Digest digest(int pointer, {Type? type, bool alreadyDereferenced = false}) {
69+
var output = AccumulatorSink<Digest>();
70+
var input = md5.startChunkedConversion(output);
71+
_buildDigest(
7172
pointer,
72-
type,
73+
input,
74+
type: type,
7375
alreadyDereferenced: alreadyDereferenced,
7476
);
77+
input.close();
78+
return output.events.single;
7579
}
7680

77-
/// Computes the identity hash of the object at [pointer] with a known [type]
78-
/// from its raw bytes.
79-
///
80-
/// If [alreadyDereferenced] is `true`, then for types which are pointers,
81-
/// [pointer] already points at the top of the object, and should not be
82-
/// followed before reading the object.
83-
int _fingerprint(
84-
_Pointer pointer,
85-
Type type, {
81+
/// See [digest].
82+
void _buildDigest(
83+
int pointer,
84+
ByteConversionSink byteSink, {
85+
Type? type,
8686
bool alreadyDereferenced = false,
8787
}) {
88+
if (type == null) {
89+
type = _readType(pointer);
90+
pointer += _typeSize;
91+
}
8892
// Dereference [pointer] if it is a pointer type, and hasn't already been
8993
// dereferenced.
9094
if (type.isPointer && !alreadyDereferenced) {
@@ -93,29 +97,30 @@ class JsonBufferBuilder {
9397

9498
switch (type) {
9599
case Type.nil:
96-
return null.hashCode;
100+
return byteSink.add(const [0]);
97101
case Type.type:
98-
return _buffer[pointer];
102+
byteSink.addSlice(_buffer, pointer, pointer + _typeSize, false);
99103
case Type.pointer:
100-
return fingerprint(pointer);
104+
_buildDigest(pointer, byteSink);
101105
case Type.uint32:
102-
return _readUint32(pointer);
106+
byteSink.addSlice(_buffer, pointer, pointer + 4, false);
103107
case Type.boolean:
104-
return _buffer[pointer];
108+
// We use [1] and [2] because [0] is `null`.
109+
byteSink.add(_readBoolean(pointer) ? const [2] : const [1]);
105110
case Type.anyPointer:
106-
return fingerprint(pointer);
111+
_buildDigest(pointer, byteSink);
107112
case Type.stringPointer:
108113
final length = _readLength(pointer);
109114
pointer += _lengthSize;
110-
return Object.hashAll(_buffer.sublist(pointer, pointer + length));
115+
byteSink.addSlice(_buffer, pointer, pointer + length, false);
111116
case Type.closedListPointer:
112-
return _ClosedList(this, pointer).fingerprint;
117+
_ClosedList(this, pointer).buildDigest(byteSink);
113118
case Type.closedMapPointer:
114-
return _ClosedMap(this, pointer, null).fingerprint;
119+
return _ClosedMap(this, pointer, null).buildDigest(byteSink);
115120
case Type.growableMapPointer:
116-
return _GrowableMap<Object?>(this, pointer, null).fingerprint;
121+
return _GrowableMap<Object?>(this, pointer, null).buildDigest(byteSink);
117122
case Type.typedMapPointer:
118-
return _TypedMap(this, pointer, null).fingerprint;
123+
return _TypedMap(this, pointer, null).buildDigest(byteSink);
119124
}
120125
}
121126

pkgs/dart_model/lib/src/json_buffer/typed_map.dart

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -468,19 +468,40 @@ class _TypedMap
468468
@override
469469
int get hashCode => Object.hash(buffer, pointer);
470470

471-
int get fingerprint {
471+
/// Up to 16 fields supported, increase this to support more.
472+
static final _indexBytes = Uint8List(2);
473+
474+
void buildDigest(ByteConversionSink byteSink) {
475+
assert(
476+
_schema._fieldSetSize <= _indexBytes.length,
477+
'Update indexBytes to support schemas with more fields',
478+
);
479+
472480
// Note: we could include the schema but don't need to. If something can
473481
// be one of multiple types, that type will be included in a `type` field
474-
// in the map.
475-
var hash = 0;
476-
final iterator =
477-
_schema._isAllBooleans
478-
? _AllBoolsTypedMapHashIterator(this)
479-
: _PartialTypedMapHashIterator(this);
480-
while (iterator.moveNext()) {
481-
hash = Object.hash(hash, iterator.current);
482+
// in the map
483+
if (_schema._isAllBooleans) {
484+
var iterator = _AllBoolsTypedMapPointerIterator(this);
485+
while (iterator.moveNext()) {
486+
_indexBytes[0] = iterator.current.$1;
487+
_indexBytes[1] = iterator.current.$1 >> 8;
488+
byteSink.add(_indexBytes);
489+
// We use [1] and [2] because [0] is `null`.
490+
byteSink.add(iterator.current.$2 ? const [2] : const [1]);
491+
}
492+
} else {
493+
var iterator = _PartialTypedMapPointerIterator(this);
494+
while (iterator.moveNext()) {
495+
_indexBytes[0] = iterator.current.$1;
496+
_indexBytes[1] = iterator.current.$1 >> 8;
497+
byteSink.add(_indexBytes);
498+
buffer._buildDigest(
499+
iterator.current.$2,
500+
byteSink,
501+
type: iterator.current.$3,
502+
);
503+
}
482504
}
483-
return hash;
484505
}
485506
}
486507

@@ -557,13 +578,15 @@ class _PartialTypedMapEntryIterator
557578
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
558579
}
559580

560-
class _PartialTypedMapHashIterator extends _PartialTypedMapIterator<int> {
561-
_PartialTypedMapHashIterator(super._map);
581+
class _PartialTypedMapPointerIterator
582+
extends _PartialTypedMapIterator<(int index, int valuePointer, Type type)> {
583+
_PartialTypedMapPointerIterator(super._map);
562584

563585
@override
564-
int get current => Object.hash(
565-
_currentKey,
566-
_buffer._fingerprint(_valuesPointer + _offset, _schema._valueTypes[_index]),
586+
(int index, int valuePointer, Type type) get current => (
587+
_index,
588+
_valuesPointer + _offset,
589+
_schema._valueTypes[_index],
567590
);
568591
}
569592

@@ -596,7 +619,7 @@ abstract class _AllBoolsTypedMapIterator<T> implements Iterator<T> {
596619
T get current;
597620

598621
String get _currentKey => _schema._keys[_index];
599-
Object? get _currentValue =>
622+
bool get _currentValue =>
600623
_buffer._readBit(_valuesPointer + _intOffset, _bitOffset);
601624

602625
@override
@@ -631,7 +654,7 @@ class _AllBoolsTypedMapValueIterator
631654
_AllBoolsTypedMapValueIterator(super._map);
632655

633656
@override
634-
Object? get current => _currentValue;
657+
bool get current => _currentValue;
635658
}
636659

637660
class _AllBoolsTypedMapEntryIterator
@@ -642,9 +665,10 @@ class _AllBoolsTypedMapEntryIterator
642665
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
643666
}
644667

645-
class _AllBoolsTypedMapHashIterator extends _AllBoolsTypedMapIterator<int> {
646-
_AllBoolsTypedMapHashIterator(super._map);
668+
class _AllBoolsTypedMapPointerIterator
669+
extends _AllBoolsTypedMapIterator<(int index, bool value)> {
670+
_AllBoolsTypedMapPointerIterator(super._map);
647671

648672
@override
649-
int get current => Object.hash(_currentKey, _currentValue);
673+
(int index, bool value) get current => (_index, _currentValue);
650674
}

0 commit comments

Comments
 (0)