-
Notifications
You must be signed in to change notification settings - Fork 1.7k
hashCode
for immutable objects should be memoized
#48948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
It's a perfectly rational decision to say this falls on the shoulders of the developer, just throwing it out there as a suggestion as something we might be able to do to give a boost to many apps. |
Related, if we memoized |
I think there's two things here:
Marking this issue as |
Generally we can't do language promises about Usually, letting the code cache the value is safer than trying to optimize in the compiler for code reading the same getter twice, since we don't know why it does so (it might be a mutable object that has been mutated, that's completely invisible to the type system). |
|
Yea, that makes sense that it would be tricky. As for calculating |
Having hash code change is almost certainly a bug. This is why the lints to only override hash code on immutable objets exist. |
Yea, I think what @lrhn was saying was we can't know for certain what people are doing in
The premise for the change was that it would happen for immutable objects. In the case I was looking at |
So let me put this differently: We already have the problem @lrhn is talking about when using sets or maps, for example void main() {
final fooSet = <MutableFoo>{};
MutableFoo a = MutableFoo(1);
MutableFoo b = MutableFoo(1);
MutableFoo c = MutableFoo(2);
fooSet.add(a);
print(fooSet);
a.i = 2;
fooSet.add(b);
b.i = 2;
print(fooSet);
fooSet.add(c);
print(fooSet); // {MutableFoo(2), MutableFoo(2), MutableFoo(2)}
}
class MutableFoo {
MutableFoo(this.i);
int i;
@override
int get hashCode => i;
@override
bool operator==(Object other) => other is MutableFoo && other.i == i;
@override
String toString() => 'MutableFoo($i)';
} Gets you a set where its three elements all currently have identical hash codes and evaluate as equal. |
I tried to implement the memoization in the user code and in my case isn't actually possible to implement because we don't allow late fields on const classes, filed an issue: dart-lang/language#2225 |
I tried to implement calculating the |
The way to store values on const classes is an To cache the hash code, do: class MyConst {
final something;
final other;
const MyConst(this.something, this.other);
int get hashCode => _hash[this] ??= Object.hash(something, other);
bool operator==(Object other) =>
other is MyConst && something = other.something && this.other = other.other;
static final _hash = Expando<int>();
} There are multiple ways this can break. First of all, you don't know the object is deeply immutable. You can do non- Also, even a deeply immutable object can pretend to be mutable using an class MyTrickyConst {
final something;
MyTrickyConst(this.something);
static final Expando<Object> _other;
Object? get other => _other[this];
set other(Object? value) { other[this] = value; }
int get hashCode => Object.hash(something, other);
bool operator==(Object other) =>
other is MyTrickyConst && something = other.something && this.other = other.other;
} This object may be deeply immutable when created using That means that event if the compiler could determine with 100% certainty that an object is created by a const operation, it can't know that the You can optimize your own hashCode. You cannot make assumptions about other classes, because the language features of Dart ensures that nothing can actually be assumed about an object's run-time behavior from just its static type. |
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: #48948 Change-Id: Ia3ccff9e592d675b4922ac78c4aa7ee0287ecb50 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243623 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Daco Harkes <[email protected]>
Closing this issue since the fix to double call to hashCode has landed and it has been shown that given how Dart works memoizing without direction from the user would not be possible. |
This reverts commit 438c1ed. Reason for revert: b/231617607 and b/230945329. Will reland after b/230945329 is addressed. 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: #48948 > Change-Id: Ia3ccff9e592d675b4922ac78c4aa7ee0287ecb50 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243623 > Reviewed-by: Martin Kustermann <[email protected]> > Commit-Queue: Daco Harkes <[email protected]> [email protected],[email protected] Change-Id: I214251b65ea89e7f729564a125e226f2e6d580c0 No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: #48948 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243900 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Daco Harkes <[email protected]> Bot-Commit: Rubber Stamper <[email protected]>
@dcharkes the CL for only calling Nice work. I'm not sure if anything else in that roll could have contributed also to that drop. |
@gaaclarke we reverted the day after because it made b/230945329 much more likely to trigger, and will reland as soon as that bug is addressed. So if the build-time stayed down it wasn't this bug (unless no rolls happened that include the revert). |
As I have mentioned in the chat the 10% improvement is more likely coming from d856e05 |
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: #48948 > Change-Id: Ia3ccff9e592d675b4922ac78c4aa7ee0287ecb50 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243623 > Reviewed-by: Martin Kustermann <[email protected]> > Commit-Queue: Daco Harkes <[email protected]> Change-Id: I033bd7cc29fc812dccb6dccf0c3dca6e22cae2ca Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/248802 Commit-Queue: Tess Strickland <[email protected]> Auto-Submit: Daco Harkes <[email protected]> Reviewed-by: Tess Strickland <[email protected]>
This has landed. This speeds up map copying ~5%-15% in various configurations. Thanks for reporting @gaaclarke! |
I was profiling the Flutter Gallery and noticed that
Locale.hashCode
(as indart:ui.Locale
) was taking a significant amount of time because the Flutter Gallery keeps aLinkedHashMap<Locale, DisplayOption>
. Locale is intended to be immutable so there is no reason to keep calculating itshashCode
. It would be nice if we could avoid recalculatinghashCode
in this case.Reproduction code
Expected output
hashCode
is called onceActual output
hashCode
is called twiceThe text was updated successfully, but these errors were encountered: