Skip to content

Commit 1156cfe

Browse files
authored
Use values iterator and update in map equality/hash (#718)
Initially I tried using `entries` instead of a `values` iterator, but that was surprisingly slow. This appears to be a good middle ground (see history of this comment for old results). Hashing is about the same, but much faster for maps which don't have O(1) lookup. Equality is much faster. ## Benchmarks before: DeepCollectionQualityUnordered.equals(RunTime): 7427.29020979021 us. DeepCollectionQualityUnordered.hash(RunTime): 217.8173707406213 us. DeepCollectionQuality.equals(RunTime): 2653.23875 us. DeepCollectionQuality.hash(RunTime): 178.1674653887114 us. ## Benchmarks after: DeepCollectionQualityUnordered.equals(RunTime): 4435.374 us. DeepCollectionQualityUnordered.hash(RunTime): 212.8631545473818 us. DeepCollectionQuality.equals(RunTime): 1989.1746626686656 us. DeepCollectionQuality.hash(RunTime): 178.3396697902722 us.
1 parent 9ab5a18 commit 1156cfe

File tree

2 files changed

+21
-8
lines changed

2 files changed

+21
-8
lines changed

pkgs/collection/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
## 1.19.1-wip
2+
23
- Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using
34
`Map.entries`.
5+
- Optimize equality and hash code for maps by using `update` and a `values`
6+
iterator to avoid extra lookups.
47

58
## 1.19.1
69

pkgs/collection/lib/src/equality.dart

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,16 +325,19 @@ class MapEquality<K, V> implements Equality<Map<K, V>> {
325325
var length = map1.length;
326326
if (length != map2.length) return false;
327327
Map<_MapEntry, int> equalElementCounts = HashMap();
328+
var values1 = map1.values.iterator;
328329
for (var key in map1.keys) {
329-
var entry = _MapEntry(this, key, map1[key]);
330-
var count = equalElementCounts[entry] ?? 0;
331-
equalElementCounts[entry] = count + 1;
330+
var value = (values1..moveNext()).current;
331+
var entry = _MapEntry(this, key, value);
332+
equalElementCounts.update(entry, _addOne, ifAbsent: _one);
332333
}
334+
final values2 = map2.values.iterator;
333335
for (var key in map2.keys) {
334-
var entry = _MapEntry(this, key, map2[key]);
335-
var count = equalElementCounts[entry];
336-
if (count == null || count == 0) return false;
337-
equalElementCounts[entry] = count - 1;
336+
var value = (values2..moveNext()).current;
337+
var entry = _MapEntry(this, key, value);
338+
var count = equalElementCounts.update(entry, _subtractOne,
339+
ifAbsent: _negativeOne);
340+
if (count < 0) return false;
338341
}
339342
return true;
340343
}
@@ -343,9 +346,11 @@ class MapEquality<K, V> implements Equality<Map<K, V>> {
343346
int hash(Map<K, V>? map) {
344347
if (map == null) return null.hashCode;
345348
var hash = 0;
349+
var values = map.values.iterator;
346350
for (var key in map.keys) {
351+
var value = (values..moveNext()).current;
347352
var keyHash = _keyEquality.hash(key);
348-
var valueHash = _valueEquality.hash(map[key] as V);
353+
var valueHash = _valueEquality.hash(value);
349354
hash = (hash + 3 * keyHash + 7 * valueHash) & _hashMask;
350355
}
351356
hash = (hash + (hash << 3)) & _hashMask;
@@ -489,3 +494,8 @@ class CaseInsensitiveEquality implements Equality<String> {
489494
@override
490495
bool isValidKey(Object? object) => object is String;
491496
}
497+
498+
int _addOne(int i) => i + 1;
499+
int _subtractOne(int i) => i - 1;
500+
int _one() => 1;
501+
int _negativeOne() => -1;

0 commit comments

Comments
 (0)