From 80007b58c10220ab68a256d98148ea6b8c6b2ea5 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Mon, 4 Nov 2024 22:26:26 +0000 Subject: [PATCH 1/4] use .entries and .update in map equality --- pkgs/collection/lib/src/equality.dart | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pkgs/collection/lib/src/equality.dart b/pkgs/collection/lib/src/equality.dart index 0e1df23d..28874f8d 100644 --- a/pkgs/collection/lib/src/equality.dart +++ b/pkgs/collection/lib/src/equality.dart @@ -325,16 +325,15 @@ class MapEquality implements Equality> { var length = map1.length; if (length != map2.length) return false; Map<_MapEntry, int> equalElementCounts = HashMap(); - for (var key in map1.keys) { - var entry = _MapEntry(this, key, map1[key]); - var count = equalElementCounts[entry] ?? 0; - equalElementCounts[entry] = count + 1; + for (var MapEntry(:key, :value) in map1.entries) { + var entry = _MapEntry(this, key, value); + equalElementCounts.update(entry, (i) => i + 1, ifAbsent: () => 1); } - for (var key in map2.keys) { - var entry = _MapEntry(this, key, map2[key]); - var count = equalElementCounts[entry]; - if (count == null || count == 0) return false; - equalElementCounts[entry] = count - 1; + for (var MapEntry(:key, :value) in map2.entries) { + var entry = _MapEntry(this, key, value); + var count = + equalElementCounts.update(entry, (i) => i - 1, ifAbsent: () => -1); + if (count < 0) return false; } return true; } @@ -343,9 +342,9 @@ class MapEquality implements Equality> { int hash(Map? map) { if (map == null) return null.hashCode; var hash = 0; - for (var key in map.keys) { + for (var MapEntry(:key, :value) in map.entries) { var keyHash = _keyEquality.hash(key); - var valueHash = _valueEquality.hash(map[key] as V); + var valueHash = _valueEquality.hash(value); hash = (hash + 3 * keyHash + 7 * valueHash) & _hashMask; } hash = (hash + (hash << 3)) & _hashMask; From 6b19fb09e452352f5211d7afe732397714f8c62b Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Tue, 5 Nov 2024 17:53:39 +0000 Subject: [PATCH 2/4] add changelog entry --- pkgs/collection/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkgs/collection/CHANGELOG.md b/pkgs/collection/CHANGELOG.md index 239efd57..ffac9249 100644 --- a/pkgs/collection/CHANGELOG.md +++ b/pkgs/collection/CHANGELOG.md @@ -1,6 +1,11 @@ ## 1.19.1-wip + - Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using `Map.entries`. +- Use `entries` and `update` in map equality implementations. + - Speeds up all equality checks. + - Speeds up implementations for maps with slow lookup, at a slight cost for + other implementations. ## 1.19.1 From 314c1e11dff5e0351698e6f82d4c7b399b72ed34 Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Tue, 5 Nov 2024 18:06:30 +0000 Subject: [PATCH 3/4] use values iterator --- pkgs/collection/CHANGELOG.md | 6 ++---- pkgs/collection/lib/src/equality.dart | 12 +++++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pkgs/collection/CHANGELOG.md b/pkgs/collection/CHANGELOG.md index ffac9249..b6a9c82b 100644 --- a/pkgs/collection/CHANGELOG.md +++ b/pkgs/collection/CHANGELOG.md @@ -2,10 +2,8 @@ - Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using `Map.entries`. -- Use `entries` and `update` in map equality implementations. - - Speeds up all equality checks. - - Speeds up implementations for maps with slow lookup, at a slight cost for - other implementations. +- Optimize equality and hash code for maps by using `update` and a `values` + iterator to avoid extra lookups. ## 1.19.1 diff --git a/pkgs/collection/lib/src/equality.dart b/pkgs/collection/lib/src/equality.dart index 28874f8d..4ed1c2a5 100644 --- a/pkgs/collection/lib/src/equality.dart +++ b/pkgs/collection/lib/src/equality.dart @@ -325,11 +325,15 @@ class MapEquality implements Equality> { var length = map1.length; if (length != map2.length) return false; Map<_MapEntry, int> equalElementCounts = HashMap(); - for (var MapEntry(:key, :value) in map1.entries) { + var values1 = map1.values.iterator; + for (var key in map1.keys) { + var value = (values1..moveNext()).current; var entry = _MapEntry(this, key, value); equalElementCounts.update(entry, (i) => i + 1, ifAbsent: () => 1); } - for (var MapEntry(:key, :value) in map2.entries) { + final values2 = map2.values.iterator; + for (var key in map2.keys) { + var value = (values2..moveNext()).current; var entry = _MapEntry(this, key, value); var count = equalElementCounts.update(entry, (i) => i - 1, ifAbsent: () => -1); @@ -342,7 +346,9 @@ class MapEquality implements Equality> { int hash(Map? map) { if (map == null) return null.hashCode; var hash = 0; - for (var MapEntry(:key, :value) in map.entries) { + var values = map.values.iterator; + for (var key in map.keys) { + var value = (values..moveNext()).current; var keyHash = _keyEquality.hash(key); var valueHash = _valueEquality.hash(value); hash = (hash + 3 * keyHash + 7 * valueHash) & _hashMask; From 04e12bbf6d3f576be5a849afc6680d83aa782b8b Mon Sep 17 00:00:00 2001 From: Jake Macdonald Date: Wed, 6 Nov 2024 15:11:11 +0000 Subject: [PATCH 4/4] add static helpers and use tearoffs --- pkgs/collection/lib/src/equality.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkgs/collection/lib/src/equality.dart b/pkgs/collection/lib/src/equality.dart index 4ed1c2a5..1e2f02ae 100644 --- a/pkgs/collection/lib/src/equality.dart +++ b/pkgs/collection/lib/src/equality.dart @@ -329,14 +329,14 @@ class MapEquality implements Equality> { for (var key in map1.keys) { var value = (values1..moveNext()).current; var entry = _MapEntry(this, key, value); - equalElementCounts.update(entry, (i) => i + 1, ifAbsent: () => 1); + equalElementCounts.update(entry, _addOne, ifAbsent: _one); } final values2 = map2.values.iterator; for (var key in map2.keys) { var value = (values2..moveNext()).current; var entry = _MapEntry(this, key, value); - var count = - equalElementCounts.update(entry, (i) => i - 1, ifAbsent: () => -1); + var count = equalElementCounts.update(entry, _subtractOne, + ifAbsent: _negativeOne); if (count < 0) return false; } return true; @@ -494,3 +494,8 @@ class CaseInsensitiveEquality implements Equality { @override bool isValidKey(Object? object) => object is String; } + +int _addOne(int i) => i + 1; +int _subtractOne(int i) => i - 1; +int _one() => 1; +int _negativeOne() => -1;