Reland "[vm] Only call `.hashCode` once when adding to `Map` and `Set`"
Relanding after https://dart-review.googlesource.com/c/sdk/+/244200.
Original change's description:
> [vm] Only call `.hashCode` once when adding to `Map` and `Set`
>
> The methods to add to hash maps and hash sets are recursive: if the
> index needs to be rehashed then the same method is called again
> after rehashing.
>
> This CL nests the actual implementation in a private method that takes
> the full hashCode as an extra argument.
>
> No significant code size or run time changes are reported on our
> benchmarks. (Our benchmarks do not contain purposefully slow hashCodes.)
>
> Note that hashCode can be called again later if rehashing of the index
> is required on adding subsequent elements.
>
> Bug: https://github.com/dart-lang/sdk/issues/48948
> Change-Id: Ia3ccff9e592d675b4922ac78c4aa7ee0287ecb50
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243623
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Daco Harkes <dacoharkes@google.com>
Change-Id: I033bd7cc29fc812dccb6dccf0c3dca6e22cae2ca
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/248802
Commit-Queue: Tess Strickland <sstrickl@google.com>
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
diff --git a/runtime/tests/vm/dart/regress_48948_test.dart b/runtime/tests/vm/dart/regress_48948_test.dart
new file mode 100644
index 0000000..2a334b3
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_48948_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+// Regression test for https://github.com/dart-lang/sdk/issues/48948.
+
+import 'package:expect/expect.dart';
+
+class Foo {
+ const Foo(this.x, this.y);
+
+ final int x;
+ final int y;
+
+ static int hashCodeCounter = 0;
+
+ @override
+ int get hashCode {
+ hashCodeCounter++;
+ return x.hashCode ^ y.hashCode;
+ }
+}
+
+void main() {
+ final Map<Foo, int> someMap = {};
+ final Set<Foo> someSet = {};
+ Expect.equals(0, Foo.hashCodeCounter);
+
+ someMap[Foo(1, 100)] = 2;
+ Expect.equals(1, Foo.hashCodeCounter);
+
+ someSet.add(Foo(1, 100));
+ Expect.equals(2, Foo.hashCodeCounter);
+}
diff --git a/runtime/tests/vm/dart_2/regress_48948_test.dart b/runtime/tests/vm/dart_2/regress_48948_test.dart
new file mode 100644
index 0000000..0e71942
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_48948_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// 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.
+
+// Regression test for https://github.com/dart-lang/sdk/issues/48948.
+
+// @dart = 2.9
+
+import 'package:expect/expect.dart';
+
+class Foo {
+ const Foo(this.x, this.y);
+
+ final int x;
+ final int y;
+
+ static int hashCodeCounter = 0;
+
+ @override
+ int get hashCode {
+ hashCodeCounter++;
+ return x.hashCode ^ y.hashCode;
+ }
+}
+
+void main() {
+ final Map<Foo, int> someMap = {};
+ final Set<Foo> someSet = {};
+ Expect.equals(0, Foo.hashCodeCounter);
+
+ someMap[Foo(1, 100)] = 2;
+ Expect.equals(1, Foo.hashCodeCounter);
+
+ someSet.add(Foo(1, 100));
+ Expect.equals(2, Foo.hashCodeCounter);
+}
diff --git a/sdk/lib/_internal/vm/lib/compact_hash.dart b/sdk/lib/_internal/vm/lib/compact_hash.dart
index 8a179fb6..16d85b2 100644
--- a/sdk/lib/_internal/vm/lib/compact_hash.dart
+++ b/sdk/lib/_internal/vm/lib/compact_hash.dart
@@ -345,7 +345,6 @@
for (int j = 0; j < _usedData; j += 2) {
final key = _data[j];
- final value = _data[j + 1];
final fullHash = _hashCode(key);
final hashPattern = _HashBase._hashPattern(fullHash, hashMask, size);
@@ -452,10 +451,10 @@
}
}
- void _insert(K key, V value, int hashPattern, int i) {
+ void _insert(K key, V value, int fullHash, int hashPattern, int i) {
if (_usedData == _data.length) {
_rehash();
- this[key] = value;
+ _set(key, value, fullHash);
} else {
assert(1 <= hashPattern && hashPattern < (1 << 32));
final int index = _usedData >> 1;
@@ -496,8 +495,12 @@
}
void operator []=(K key, V value) {
- final int size = _index.length;
final int fullHash = _hashCode(key);
+ _set(key, value, fullHash);
+ }
+
+ void _set(K key, V value, int fullHash) {
+ final int size = _index.length;
final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size);
final int d =
_findValueOrInsertPoint(key, fullHash, hashPattern, size, _index);
@@ -505,7 +508,7 @@
_data[d] = value;
} else {
final int i = -d;
- _insert(key, value, hashPattern, i);
+ _insert(key, value, fullHash, hashPattern, i);
}
}
@@ -526,7 +529,7 @@
this[key] = value;
} else {
final int i = -d;
- _insert(key, value, hashPattern, i);
+ _insert(key, value, fullHash, hashPattern, i);
}
return value;
}
@@ -832,10 +835,14 @@
}
bool add(E key) {
+ final int fullHash = _hashCode(key);
+ return _add(key, fullHash);
+ }
+
+ bool _add(E key, int fullHash) {
final int size = _index.length;
final int sizeMask = size - 1;
final int maxEntries = size >> 1;
- final int fullHash = _hashCode(key);
final int hashPattern = _HashBase._hashPattern(fullHash, _hashMask, size);
int i = _HashBase._firstProbe(fullHash, sizeMask);
int firstDeleted = -1;
@@ -856,7 +863,7 @@
}
if (_usedData == _data.length) {
_rehash();
- add(key);
+ _add(key, fullHash);
} else {
final int insertionPoint = (firstDeleted >= 0) ? firstDeleted : i;
assert(1 <= hashPattern && hashPattern < (1 << 32));