Storing the hashCode
MelbourneDeveloper opened this issue · comments
Immutable types don't change, so there is no need to recalculate the hashCode
repeatedly. It's possible to squeeze some performance out of the hashCode
getter by either calculating and storing it in the constructor or storing it after the first time it gets called. I made the changes to EquatableMixin
in this branch of my fork in this PR to see how it goes. It was straightforward and doesn't affect any tests. I added a test to make sure it works. I haven't made any changes to Equatable
in this draft PR.
Here is some benchmark code using benchmark_harness.
import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:equatable/equatable.dart';
class A with EquatableMixin {
A(
this.name,
this.count,
this.rate,
this.b,
);
final String name;
final int count;
final double rate;
final B b;
@override
List<Object?> get props => [
name,
count,
rate,
b,
];
}
class B with EquatableMixin {
B(
this.name,
this.count,
this.rate,
);
final String name;
final int count;
final double rate;
@override
List<Object?> get props => [
name,
count,
rate,
];
}
class TemplateBenchmark extends BenchmarkBase {
const TemplateBenchmark() : super('Template');
static void main() {
const TemplateBenchmark().report();
}
// The benchmark code.
@override
void run() {
A a = A(
'abcdefghijklmnop',
1,
3243434.434,
B(
'kjsdfksdfkhsdf',
2,
3243434.434,
),
);
final hash1 = a.hashCode;
final hash2 = a.hashCode;
}
@override
void setup() {}
@override
void teardown() {}
}
void main() {
TemplateBenchmark.main();
}
On my old Linux laptop, the average for the original is:
Template(RunTime): 3.0033961621384506 us.
New version:
Template(RunTime): 1.535801732099134 us.
I can do some tests on macOS and Windows if that is necessary. You can see that there is a fair bit of difference when there are multiple calls to get the hashCode
.
The obvious downside is that it will cause issues with any types that are not immutable, but they will have issues anyway.
I created a package doing exactly that, a couple of months back: https://github.com/FaFre/fast_equatable maybe you can find some inspirations in there.
By adding a mutable property on EquatableMixin
, all instances would no longer be immutable.
For example:
import 'package:meta/meta.dart';
mixin EquatableMixin on Object {
int? _hashCode;
}
@immutable
class Person with EquatableMixin {}
The above code will result in a warning because Person
should be immutable
but has non-final fields:
@felangel I don't think anything can be done to fully satisfy the immutable requirement for the mixin.
There would be a similar issue with the Equatable
class. Technically this is still immutable:
But, the class loses the const
constructor. Still, there is something unwholesome about repeatedly hashing data that doesn't change.