Skip to content

Commit

Permalink
Use Object.hash to hash raw bytes (#134)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jakemac53 authored Nov 7, 2024
1 parent 19dda73 commit d1d9555
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 63 deletions.
4 changes: 2 additions & 2 deletions pkgs/_macro_host/lib/src/macro_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class MacroResultsCache {
.skip(1)
.fold(queryResults.first.response,
(model, next) => model.mergeWith(next.response))
.identityHash,
.fingerprint,
response: response
);
}
Expand All @@ -61,7 +61,7 @@ class MacroResultsCache {
.skip(1)
.fold(queryResults.first.model,
(model, next) => model.mergeWith(next.model))
.identityHash;
.fingerprint;
if (newResultsHash != cached.resultsHash) {
_cache.remove(cacheKey);
return null;
Expand Down
9 changes: 6 additions & 3 deletions pkgs/dart_model/lib/src/dart_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:collection/collection.dart';

import 'dart_model.g.dart';
import 'json_buffer/json_buffer_builder.dart';
import 'lazy_merged_map.dart';
Expand Down Expand Up @@ -33,7 +31,12 @@ extension ModelExtension on Model {
/// An identity hash for `this`, used for comparing query results.
///
/// TODO: A faster/better implementation?
int get identityHash => const DeepCollectionEquality().hash(node);
int get fingerprint {
// TODO: Implementation for non-buffer maps?
var node = this.node as MapInBuffer;
return node.buffer.fingerprint(node.pointer,
type: Type.typedMapPointer, alreadyDereferenced: true);
}

/// Looks up [name] in `this`.
///
Expand Down
20 changes: 18 additions & 2 deletions pkgs/dart_model/lib/src/json_buffer/closed_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,19 @@ class _ClosedList with ListMixin<Object?> {
set length(int length) {
throw UnsupportedError('This JsonBufferBuilder list is read-only.');
}

int get fingerprint {
var iterator = _ClosedListHashIterator(_buffer, _pointer, length);
var hash = 0;
while (iterator.moveNext()) {
hash = Object.hash(hash, iterator.current);
}
return hash;
}
}

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

@override
Object? get current => _buffer._readAny(_pointer);
T get current => _buffer._readAny(_pointer) as T;

@override
bool moveNext() {
Expand All @@ -101,3 +110,10 @@ class _ClosedListIterator implements Iterator<Object?> {
return _pointer != _last;
}
}

class _ClosedListHashIterator extends _ClosedListIterator<int> {
_ClosedListHashIterator(super.buffer, super.pointer, super.length);

@override
int get current => _buffer.fingerprint(_pointer);
}
47 changes: 33 additions & 14 deletions pkgs/dart_model/lib/src/json_buffer/closed_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,15 @@ class _ClosedMap
implements MapInBuffer {
@override
final JsonBufferBuilder buffer;
final _Pointer _pointer;
@override
final _Pointer pointer;
@override
final Map<String, Object?>? parent;
@override
final int length;

_ClosedMap(this.buffer, this._pointer, this.parent)
: length = buffer._readLength(_pointer);
_ClosedMap(this.buffer, this.pointer, this.parent)
: length = buffer._readLength(pointer);

@override
Object? operator [](Object? key) {
Expand All @@ -75,18 +76,18 @@ class _ClosedMap

@override
late final Iterable<String> keys = _IteratorFunctionIterable(
() => _ClosedMapKeyIterator(buffer, this, _pointer, length),
() => _ClosedMapKeyIterator(buffer, this, pointer, length),
length: length);

@override
late final Iterable<Object?> values = _IteratorFunctionIterable(
() => _ClosedMapValueIterator(buffer, this, _pointer, length),
() => _ClosedMapValueIterator(buffer, this, pointer, length),
length: length);

@override
late final Iterable<MapEntry<String, Object?>> entries =
_IteratorFunctionIterable(
() => _ClosedMapEntryIterator(buffer, this, _pointer, length),
() => _ClosedMapEntryIterator(buffer, this, pointer, length),
length: length);

@override
Expand All @@ -109,18 +110,25 @@ class _ClosedMap

@override
bool operator ==(Object other) =>
other is _ClosedMap &&
other.buffer == buffer &&
other._pointer == _pointer;
other is _ClosedMap && other.buffer == buffer && other.pointer == pointer;

@override
int get hashCode => Object.hash(buffer, _pointer);
int get hashCode => Object.hash(buffer, pointer);

int get fingerprint {
var iterator = _ClosedMapHashIterator(buffer, null, pointer, length);
var hash = 0;
while (iterator.moveNext()) {
hash = Object.hash(hash, iterator.current);
}
return hash;
}
}

/// `Iterator` that reads a "closed map" in a [JsonBufferBuilder].
abstract class _ClosedMapIterator<T> implements Iterator<T> {
final JsonBufferBuilder _buffer;
final _ClosedMap _parent;
final _ClosedMap? _parent;
final _Pointer _last;

_Pointer _pointer;
Expand Down Expand Up @@ -148,15 +156,15 @@ abstract class _ClosedMapIterator<T> implements Iterator<T> {

class _ClosedMapKeyIterator extends _ClosedMapIterator<String> {
_ClosedMapKeyIterator(
super._buffer, super._porent, super.pointer, super.length);
super._buffer, super._parent, super.pointer, super.length);

@override
String get current => _currentKey;
}

class _ClosedMapValueIterator extends _ClosedMapIterator<Object?> {
_ClosedMapValueIterator(
super._buffer, super._porent, super.pointer, super.length);
super._buffer, super._parent, super.pointer, super.length);

@override
Object? get current => _currentValue;
Expand All @@ -165,8 +173,19 @@ class _ClosedMapValueIterator extends _ClosedMapIterator<Object?> {
class _ClosedMapEntryIterator
extends _ClosedMapIterator<MapEntry<String, Object?>> {
_ClosedMapEntryIterator(
super._buffer, super._porent, super.pointer, super.length);
super._buffer, super._parent, super.pointer, super.length);

@override
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
}

class _ClosedMapHashIterator extends _ClosedMapIterator<int> {
_ClosedMapHashIterator(
super._buffer, super._parent, super.pointer, super.length);

@override
int get current => Object.hash(
_buffer._fingerprint(_pointer, Type.stringPointer),
_buffer.fingerprint(_pointer + ClosedMaps._keySize),
);
}
54 changes: 37 additions & 17 deletions pkgs/dart_model/lib/src/json_buffer/growable_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ extension GrowableMaps on JsonBufferBuilder {
/// [createGrowableMap]. Otherwise, [UnsupportedError] is thrown.
_Pointer _pointerToGrowableMap(_GrowableMap<Object?> map) {
_checkGrowableMapOwnership(map);
return map._pointer;
return map.pointer;
}

/// Throws if [map is backed by a different buffer to `this`.
Expand All @@ -75,14 +75,15 @@ class _GrowableMap<V>
implements MapInBuffer {
@override
final JsonBufferBuilder buffer;
final _Pointer _pointer;
@override
final _Pointer pointer;
@override
final Map<String, Object?>? parent;
int _length;
_Pointer? _lastPointer;

_GrowableMap(this.buffer, this._pointer, this.parent)
: _length = buffer._readLength(_pointer + _pointerSize);
_GrowableMap(this.buffer, this.pointer, this.parent)
: _length = buffer._readLength(pointer + _pointerSize);

@override
int get length => _length;
Expand All @@ -100,17 +101,17 @@ class _GrowableMap<V>

@override
late final Iterable<String> keys = _IteratorFunctionIterable(
() => _GrowableMapKeyIterator(buffer, this, _pointer),
() => _GrowableMapKeyIterator(buffer, this, pointer),
length: length);

@override
late final Iterable<V> values = _IteratorFunctionIterable(
() => _GrowableMapValueIterator<V>(buffer, this, _pointer),
() => _GrowableMapValueIterator<V>(buffer, this, pointer),
length: length);

@override
late final Iterable<MapEntry<String, V>> entries = _IteratorFunctionIterable(
() => _GrowableMapEntryIterator(buffer, this, _pointer),
() => _GrowableMapEntryIterator(buffer, this, pointer),
length: length);

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

// If `_lastPointer` is not set yet, walk the map to find the end of it.
if (_lastPointer == null) {
final iterator = _GrowableMapEntryIterator<V>(buffer, this, _pointer);
_lastPointer = _pointer;
final iterator = _GrowableMapEntryIterator<V>(buffer, this, pointer);
_lastPointer = pointer;
while (iterator.moveNext()) {
_lastPointer = iterator._pointer;
}
}

// Reserve and write the new node.
final pointer = buffer._reserve(GrowableMaps._entrySize);
final entryPointer = pointer + _pointerSize;
final newPointer = buffer._reserve(GrowableMaps._entrySize);
final entryPointer = newPointer + _pointerSize;
buffer._writePointer(entryPointer, buffer._pointerToString(key));
buffer._writeAny(entryPointer + _pointerSize, value);

// Point to the new node in the previous node.
buffer._writePointer(_lastPointer!, pointer);
buffer._writePointer(_lastPointer!, newPointer);
// Update `_lastPointer` to the new node.
_lastPointer = pointer;
_lastPointer = newPointer;

// Update length.
++_length;
buffer._writeLength(_pointer + _pointerSize, length, allowOverwrite: true);
buffer._writeLength(pointer + _pointerSize, length, allowOverwrite: true);
buffer._explanations?.pop();
}

Expand All @@ -163,16 +164,25 @@ class _GrowableMap<V>
bool operator ==(Object other) =>
other is _GrowableMap &&
other.buffer == buffer &&
other._pointer == _pointer;
other.pointer == pointer;

@override
int get hashCode => Object.hash(buffer, _pointer);
int get hashCode => Object.hash(buffer, pointer);

int get fingerprint {
var iterator = _GrowableMapHashIterator(buffer, null, pointer);
var hash = 0;
while (iterator.moveNext()) {
hash = Object.hash(hash, iterator.current);
}
return hash;
}
}

/// `Iterator` that reads a "growable map" in a [JsonBufferBuilder].
abstract class _GrowableMapIterator<T> implements Iterator<T> {
final JsonBufferBuilder _buffer;
final _GrowableMap _parent;
final _GrowableMap? _parent;
_Pointer _pointer;

_GrowableMapIterator(this._buffer, this._parent, this._pointer);
Expand Down Expand Up @@ -214,3 +224,13 @@ class _GrowableMapEntryIterator<V>
@override
MapEntry<String, V> get current => MapEntry(_currentKey, _currentValue as V);
}

class _GrowableMapHashIterator extends _GrowableMapIterator<int> {
_GrowableMapHashIterator(super._buffer, super._parent, super._pointer);

@override
int get current => Object.hash(
_buffer._fingerprint(_pointer + _pointerSize, Type.stringPointer),
_buffer.fingerprint(_pointer + _pointerSize + GrowableMaps._keySize),
);
}
3 changes: 3 additions & 0 deletions pkgs/dart_model/lib/src/json_buffer/iterables.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ abstract interface class MapInBuffer {
/// The `Map` that contains this value, or `null` if this value has not been
/// added to a `Map` or is itself the root `Map`.
Map<String, Object?>? get parent;

/// A pointer to the start of this object in [buffer].
int get pointer;
}
61 changes: 61 additions & 0 deletions pkgs/dart_model/lib/src/json_buffer/json_buffer_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,67 @@ class JsonBufferBuilder {
map = createGrowableMap<Object?>();
}

/// Computes the identity hash of the object at [pointer] from its raw bytes.
///
/// Any nested pointers use the hash of the values they point to.
///
/// If [type] is provided, [pointer] should point directly at the object.
/// Otherwise a [Type] will be read first, followed by the value.
///
/// If [alreadyDereferenced] is `true`, then for types which are pointers,
/// [pointer] already points at the top of the object, and should not be
/// followed before reading the object.
int fingerprint(int pointer, {Type? type, bool alreadyDereferenced = false}) {
if (type == null) {
type = _readType(pointer);
pointer += _typeSize;
}
return _fingerprint(pointer, type,
alreadyDereferenced: alreadyDereferenced);
}

/// Computes the identity hash of the object at [pointer] with a known [type]
/// from its raw bytes.
///
/// If [alreadyDereferenced] is `true`, then for types which are pointers,
/// [pointer] already points at the top of the object, and should not be
/// followed before reading the object.
int _fingerprint(_Pointer pointer, Type type,
{bool alreadyDereferenced = false}) {
// Dereference [pointer] if it is a pointer type, and hasn't already been
// dereferenced.
if (type.isPointer && !alreadyDereferenced) {
pointer = _readPointer(pointer);
}

switch (type) {
case Type.nil:
return null.hashCode;
case Type.type:
return _buffer[pointer];
case Type.pointer:
return fingerprint(pointer);
case Type.uint32:
return _readUint32(pointer);
case Type.boolean:
return _buffer[pointer];
case Type.anyPointer:
return fingerprint(pointer);
case Type.stringPointer:
final length = _readLength(pointer);
pointer += _lengthSize;
return Object.hashAll(_buffer.sublist(pointer, pointer + length));
case Type.closedListPointer:
return _ClosedList(this, pointer).fingerprint;
case Type.closedMapPointer:
return _ClosedMap(this, pointer, null).fingerprint;
case Type.growableMapPointer:
return _GrowableMap<Object?>(this, pointer, null).fingerprint;
case Type.typedMapPointer:
return _TypedMap(this, pointer, null).fingerprint;
}
}

/// The JSON data.
///
/// The buffer is _not_ copied, unpredictable behavior will result if it is
Expand Down
Loading

0 comments on commit d1d9555

Please sign in to comment.