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

Commit d1d9555

Browse files
authored
Use Object.hash to hash raw bytes (#134)
Avoids decoding values during hashing just to re-encode them as bytes to hash, when stored as raw bytes. Ultimately this mostly just applies to Strings, but that is still impactful. We also avoid a lot of type checks, instead doing switches based on the known or encoded types. For now I am just using Object.hash and Object.hashAll but we could explore alternatives later on as well, although it would mean passing a byte sink around. This drops the total time for the large benchmark another 10% ish on my machine.
1 parent 19dda73 commit d1d9555

File tree

10 files changed

+302
-63
lines changed

10 files changed

+302
-63
lines changed

pkgs/_macro_host/lib/src/macro_cache.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class MacroResultsCache {
3636
.skip(1)
3737
.fold(queryResults.first.response,
3838
(model, next) => model.mergeWith(next.response))
39-
.identityHash,
39+
.fingerprint,
4040
response: response
4141
);
4242
}
@@ -61,7 +61,7 @@ class MacroResultsCache {
6161
.skip(1)
6262
.fold(queryResults.first.model,
6363
(model, next) => model.mergeWith(next.model))
64-
.identityHash;
64+
.fingerprint;
6565
if (newResultsHash != cached.resultsHash) {
6666
_cache.remove(cacheKey);
6767
return null;

pkgs/dart_model/lib/src/dart_model.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
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:collection/collection.dart';
6-
75
import 'dart_model.g.dart';
86
import 'json_buffer/json_buffer_builder.dart';
97
import 'lazy_merged_map.dart';
@@ -33,7 +31,12 @@ extension ModelExtension on Model {
3331
/// An identity hash for `this`, used for comparing query results.
3432
///
3533
/// TODO: A faster/better implementation?
36-
int get identityHash => const DeepCollectionEquality().hash(node);
34+
int get fingerprint {
35+
// TODO: Implementation for non-buffer maps?
36+
var node = this.node as MapInBuffer;
37+
return node.buffer.fingerprint(node.pointer,
38+
type: Type.typedMapPointer, alreadyDereferenced: true);
39+
}
3740

3841
/// Looks up [name] in `this`.
3942
///

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,19 @@ class _ClosedList with ListMixin<Object?> {
7777
set length(int length) {
7878
throw UnsupportedError('This JsonBufferBuilder list is read-only.');
7979
}
80+
81+
int get fingerprint {
82+
var iterator = _ClosedListHashIterator(_buffer, _pointer, length);
83+
var hash = 0;
84+
while (iterator.moveNext()) {
85+
hash = Object.hash(hash, iterator.current);
86+
}
87+
return hash;
88+
}
8089
}
8190

8291
/// `Iterator` that reads a "closed list" in a [JsonBufferBuilder].
83-
class _ClosedListIterator implements Iterator<Object?> {
92+
class _ClosedListIterator<T extends Object?> implements Iterator<T> {
8493
final JsonBufferBuilder _buffer;
8594
final _Pointer _last;
8695
_Pointer _pointer;
@@ -91,7 +100,7 @@ class _ClosedListIterator implements Iterator<Object?> {
91100
_pointer = pointer + _lengthSize - ClosedLists._valueSize;
92101

93102
@override
94-
Object? get current => _buffer._readAny(_pointer);
103+
T get current => _buffer._readAny(_pointer) as T;
95104

96105
@override
97106
bool moveNext() {
@@ -101,3 +110,10 @@ class _ClosedListIterator implements Iterator<Object?> {
101110
return _pointer != _last;
102111
}
103112
}
113+
114+
class _ClosedListHashIterator extends _ClosedListIterator<int> {
115+
_ClosedListHashIterator(super.buffer, super.pointer, super.length);
116+
117+
@override
118+
int get current => _buffer.fingerprint(_pointer);
119+
}

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

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,15 @@ class _ClosedMap
5555
implements MapInBuffer {
5656
@override
5757
final JsonBufferBuilder buffer;
58-
final _Pointer _pointer;
58+
@override
59+
final _Pointer pointer;
5960
@override
6061
final Map<String, Object?>? parent;
6162
@override
6263
final int length;
6364

64-
_ClosedMap(this.buffer, this._pointer, this.parent)
65-
: length = buffer._readLength(_pointer);
65+
_ClosedMap(this.buffer, this.pointer, this.parent)
66+
: length = buffer._readLength(pointer);
6667

6768
@override
6869
Object? operator [](Object? key) {
@@ -75,18 +76,18 @@ class _ClosedMap
7576

7677
@override
7778
late final Iterable<String> keys = _IteratorFunctionIterable(
78-
() => _ClosedMapKeyIterator(buffer, this, _pointer, length),
79+
() => _ClosedMapKeyIterator(buffer, this, pointer, length),
7980
length: length);
8081

8182
@override
8283
late final Iterable<Object?> values = _IteratorFunctionIterable(
83-
() => _ClosedMapValueIterator(buffer, this, _pointer, length),
84+
() => _ClosedMapValueIterator(buffer, this, pointer, length),
8485
length: length);
8586

8687
@override
8788
late final Iterable<MapEntry<String, Object?>> entries =
8889
_IteratorFunctionIterable(
89-
() => _ClosedMapEntryIterator(buffer, this, _pointer, length),
90+
() => _ClosedMapEntryIterator(buffer, this, pointer, length),
9091
length: length);
9192

9293
@override
@@ -109,18 +110,25 @@ class _ClosedMap
109110

110111
@override
111112
bool operator ==(Object other) =>
112-
other is _ClosedMap &&
113-
other.buffer == buffer &&
114-
other._pointer == _pointer;
113+
other is _ClosedMap && other.buffer == buffer && other.pointer == pointer;
115114

116115
@override
117-
int get hashCode => Object.hash(buffer, _pointer);
116+
int get hashCode => Object.hash(buffer, pointer);
117+
118+
int get fingerprint {
119+
var iterator = _ClosedMapHashIterator(buffer, null, pointer, length);
120+
var hash = 0;
121+
while (iterator.moveNext()) {
122+
hash = Object.hash(hash, iterator.current);
123+
}
124+
return hash;
125+
}
118126
}
119127

120128
/// `Iterator` that reads a "closed map" in a [JsonBufferBuilder].
121129
abstract class _ClosedMapIterator<T> implements Iterator<T> {
122130
final JsonBufferBuilder _buffer;
123-
final _ClosedMap _parent;
131+
final _ClosedMap? _parent;
124132
final _Pointer _last;
125133

126134
_Pointer _pointer;
@@ -148,15 +156,15 @@ abstract class _ClosedMapIterator<T> implements Iterator<T> {
148156

149157
class _ClosedMapKeyIterator extends _ClosedMapIterator<String> {
150158
_ClosedMapKeyIterator(
151-
super._buffer, super._porent, super.pointer, super.length);
159+
super._buffer, super._parent, super.pointer, super.length);
152160

153161
@override
154162
String get current => _currentKey;
155163
}
156164

157165
class _ClosedMapValueIterator extends _ClosedMapIterator<Object?> {
158166
_ClosedMapValueIterator(
159-
super._buffer, super._porent, super.pointer, super.length);
167+
super._buffer, super._parent, super.pointer, super.length);
160168

161169
@override
162170
Object? get current => _currentValue;
@@ -165,8 +173,19 @@ class _ClosedMapValueIterator extends _ClosedMapIterator<Object?> {
165173
class _ClosedMapEntryIterator
166174
extends _ClosedMapIterator<MapEntry<String, Object?>> {
167175
_ClosedMapEntryIterator(
168-
super._buffer, super._porent, super.pointer, super.length);
176+
super._buffer, super._parent, super.pointer, super.length);
169177

170178
@override
171179
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
172180
}
181+
182+
class _ClosedMapHashIterator extends _ClosedMapIterator<int> {
183+
_ClosedMapHashIterator(
184+
super._buffer, super._parent, super.pointer, super.length);
185+
186+
@override
187+
int get current => Object.hash(
188+
_buffer._fingerprint(_pointer, Type.stringPointer),
189+
_buffer.fingerprint(_pointer + ClosedMaps._keySize),
190+
);
191+
}

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

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ extension GrowableMaps on JsonBufferBuilder {
5252
/// [createGrowableMap]. Otherwise, [UnsupportedError] is thrown.
5353
_Pointer _pointerToGrowableMap(_GrowableMap<Object?> map) {
5454
_checkGrowableMapOwnership(map);
55-
return map._pointer;
55+
return map.pointer;
5656
}
5757

5858
/// Throws if [map is backed by a different buffer to `this`.
@@ -75,14 +75,15 @@ class _GrowableMap<V>
7575
implements MapInBuffer {
7676
@override
7777
final JsonBufferBuilder buffer;
78-
final _Pointer _pointer;
78+
@override
79+
final _Pointer pointer;
7980
@override
8081
final Map<String, Object?>? parent;
8182
int _length;
8283
_Pointer? _lastPointer;
8384

84-
_GrowableMap(this.buffer, this._pointer, this.parent)
85-
: _length = buffer._readLength(_pointer + _pointerSize);
85+
_GrowableMap(this.buffer, this.pointer, this.parent)
86+
: _length = buffer._readLength(pointer + _pointerSize);
8687

8788
@override
8889
int get length => _length;
@@ -100,17 +101,17 @@ class _GrowableMap<V>
100101

101102
@override
102103
late final Iterable<String> keys = _IteratorFunctionIterable(
103-
() => _GrowableMapKeyIterator(buffer, this, _pointer),
104+
() => _GrowableMapKeyIterator(buffer, this, pointer),
104105
length: length);
105106

106107
@override
107108
late final Iterable<V> values = _IteratorFunctionIterable(
108-
() => _GrowableMapValueIterator<V>(buffer, this, _pointer),
109+
() => _GrowableMapValueIterator<V>(buffer, this, pointer),
109110
length: length);
110111

111112
@override
112113
late final Iterable<MapEntry<String, V>> entries = _IteratorFunctionIterable(
113-
() => _GrowableMapEntryIterator(buffer, this, _pointer),
114+
() => _GrowableMapEntryIterator(buffer, this, pointer),
114115
length: length);
115116

116117
/// Add [value] to the map with key [key].
@@ -125,27 +126,27 @@ class _GrowableMap<V>
125126

126127
// If `_lastPointer` is not set yet, walk the map to find the end of it.
127128
if (_lastPointer == null) {
128-
final iterator = _GrowableMapEntryIterator<V>(buffer, this, _pointer);
129-
_lastPointer = _pointer;
129+
final iterator = _GrowableMapEntryIterator<V>(buffer, this, pointer);
130+
_lastPointer = pointer;
130131
while (iterator.moveNext()) {
131132
_lastPointer = iterator._pointer;
132133
}
133134
}
134135

135136
// Reserve and write the new node.
136-
final pointer = buffer._reserve(GrowableMaps._entrySize);
137-
final entryPointer = pointer + _pointerSize;
137+
final newPointer = buffer._reserve(GrowableMaps._entrySize);
138+
final entryPointer = newPointer + _pointerSize;
138139
buffer._writePointer(entryPointer, buffer._pointerToString(key));
139140
buffer._writeAny(entryPointer + _pointerSize, value);
140141

141142
// Point to the new node in the previous node.
142-
buffer._writePointer(_lastPointer!, pointer);
143+
buffer._writePointer(_lastPointer!, newPointer);
143144
// Update `_lastPointer` to the new node.
144-
_lastPointer = pointer;
145+
_lastPointer = newPointer;
145146

146147
// Update length.
147148
++_length;
148-
buffer._writeLength(_pointer + _pointerSize, length, allowOverwrite: true);
149+
buffer._writeLength(pointer + _pointerSize, length, allowOverwrite: true);
149150
buffer._explanations?.pop();
150151
}
151152

@@ -163,16 +164,25 @@ class _GrowableMap<V>
163164
bool operator ==(Object other) =>
164165
other is _GrowableMap &&
165166
other.buffer == buffer &&
166-
other._pointer == _pointer;
167+
other.pointer == pointer;
167168

168169
@override
169-
int get hashCode => Object.hash(buffer, _pointer);
170+
int get hashCode => Object.hash(buffer, pointer);
171+
172+
int get fingerprint {
173+
var iterator = _GrowableMapHashIterator(buffer, null, pointer);
174+
var hash = 0;
175+
while (iterator.moveNext()) {
176+
hash = Object.hash(hash, iterator.current);
177+
}
178+
return hash;
179+
}
170180
}
171181

172182
/// `Iterator` that reads a "growable map" in a [JsonBufferBuilder].
173183
abstract class _GrowableMapIterator<T> implements Iterator<T> {
174184
final JsonBufferBuilder _buffer;
175-
final _GrowableMap _parent;
185+
final _GrowableMap? _parent;
176186
_Pointer _pointer;
177187

178188
_GrowableMapIterator(this._buffer, this._parent, this._pointer);
@@ -214,3 +224,13 @@ class _GrowableMapEntryIterator<V>
214224
@override
215225
MapEntry<String, V> get current => MapEntry(_currentKey, _currentValue as V);
216226
}
227+
228+
class _GrowableMapHashIterator extends _GrowableMapIterator<int> {
229+
_GrowableMapHashIterator(super._buffer, super._parent, super._pointer);
230+
231+
@override
232+
int get current => Object.hash(
233+
_buffer._fingerprint(_pointer + _pointerSize, Type.stringPointer),
234+
_buffer.fingerprint(_pointer + _pointerSize + GrowableMaps._keySize),
235+
);
236+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,7 @@ abstract interface class MapInBuffer {
4848
/// The `Map` that contains this value, or `null` if this value has not been
4949
/// added to a `Map` or is itself the root `Map`.
5050
Map<String, Object?>? get parent;
51+
52+
/// A pointer to the start of this object in [buffer].
53+
int get pointer;
5154
}

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,67 @@ class JsonBufferBuilder {
5252
map = createGrowableMap<Object?>();
5353
}
5454

55+
/// Computes the identity hash of the object at [pointer] from its raw bytes.
56+
///
57+
/// Any nested pointers use the hash of the values they point to.
58+
///
59+
/// If [type] is provided, [pointer] should point directly at the object.
60+
/// Otherwise a [Type] will be read first, followed by the value.
61+
///
62+
/// If [alreadyDereferenced] is `true`, then for types which are pointers,
63+
/// [pointer] already points at the top of the object, and should not be
64+
/// 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(pointer, type,
71+
alreadyDereferenced: alreadyDereferenced);
72+
}
73+
74+
/// Computes the identity hash of the object at [pointer] with a known [type]
75+
/// from its raw bytes.
76+
///
77+
/// If [alreadyDereferenced] is `true`, then for types which are pointers,
78+
/// [pointer] already points at the top of the object, and should not be
79+
/// followed before reading the object.
80+
int _fingerprint(_Pointer pointer, Type type,
81+
{bool alreadyDereferenced = false}) {
82+
// Dereference [pointer] if it is a pointer type, and hasn't already been
83+
// dereferenced.
84+
if (type.isPointer && !alreadyDereferenced) {
85+
pointer = _readPointer(pointer);
86+
}
87+
88+
switch (type) {
89+
case Type.nil:
90+
return null.hashCode;
91+
case Type.type:
92+
return _buffer[pointer];
93+
case Type.pointer:
94+
return fingerprint(pointer);
95+
case Type.uint32:
96+
return _readUint32(pointer);
97+
case Type.boolean:
98+
return _buffer[pointer];
99+
case Type.anyPointer:
100+
return fingerprint(pointer);
101+
case Type.stringPointer:
102+
final length = _readLength(pointer);
103+
pointer += _lengthSize;
104+
return Object.hashAll(_buffer.sublist(pointer, pointer + length));
105+
case Type.closedListPointer:
106+
return _ClosedList(this, pointer).fingerprint;
107+
case Type.closedMapPointer:
108+
return _ClosedMap(this, pointer, null).fingerprint;
109+
case Type.growableMapPointer:
110+
return _GrowableMap<Object?>(this, pointer, null).fingerprint;
111+
case Type.typedMapPointer:
112+
return _TypedMap(this, pointer, null).fingerprint;
113+
}
114+
}
115+
55116
/// The JSON data.
56117
///
57118
/// The buffer is _not_ copied, unpredictable behavior will result if it is

0 commit comments

Comments
 (0)