Skip to content

Commit a679c3d

Browse files
perf: improve equality comparison performance (#173)
Co-authored-by: Felix Angelov <[email protected]>
1 parent fc5cf81 commit a679c3d

File tree

6 files changed

+434
-60
lines changed

6 files changed

+434
-60
lines changed

benchmarks/README.md

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,82 +11,160 @@ Benchmarks used to measure the performance of equality comparisons using `packag
1111

1212
## Results
1313

14+
**JIT**
15+
1416
```
1517
EmptyEquatable
16-
total runs: 2 064 037
18+
total runs: 2 729 471
1719
total time: 2.0000 s
1820
average run: 0 μs
1921
runs/second: Infinity
20-
units: 100
22+
units: 100
2123
units/second: Infinity
2224
time per unit: 0.0000 μs
2325
2426
PrimitiveEquatable
25-
total runs: 729 555
27+
total runs: 669 972
2628
total time: 2.0000 s
2729
average run: 2 μs
28-
runs/second: 500 000
29-
units: 100
30-
units/second: 50 000 000
30+
runs/second: 500 000
31+
units: 100
32+
units/second: 50 000 000
3133
time per unit: 0.0200 μs
3234
3335
CollectionEquatable (static, small)
34-
total runs: 51 944
36+
total runs: 144 932
3537
total time: 2.0000 s
36-
average run: 38 μs
37-
runs/second: 26 316
38-
units: 100
39-
units/second: 2 631 579
40-
time per unit: 0.3800 μs
38+
average run: 13 μs
39+
runs/second: 76 923
40+
units: 100
41+
units/second: 7 692 308
42+
time per unit: 0.1300 μs
4143
4244
CollectionEquatable (static, medium)
43-
total runs: 44 572
45+
total runs: 84 533
4446
total time: 2.0000 s
45-
average run: 44 μs
46-
runs/second: 22 727
47-
units: 100
48-
units/second: 2 272 727
49-
time per unit: 0.4400 μs
47+
average run: 23 μs
48+
runs/second: 43 478
49+
units: 100
50+
units/second: 4 347 826
51+
time per unit: 0.2300 μs
5052
5153
CollectionEquatable (static, large)
52-
total runs: 21 027
54+
total runs: 16 457
5355
total time: 2.0001 s
54-
average run: 95 μs
55-
runs/second: 10 526
56-
units: 100
57-
units/second: 1 052 632
58-
time per unit: 0.9500 μs
56+
average run: 121 μs
57+
runs/second: 8 264.5
58+
units: 100
59+
units/second: 826 446
60+
time per unit: 1.2100 μs
61+
62+
CollectionEquatable (dynamic, small)
63+
total runs: 388 236
64+
total time: 2.0000 s
65+
average run: 5 μs
66+
runs/second: 200 000
67+
units: 100
68+
units/second: 20 000 000
69+
time per unit: 0.0500 μs
70+
71+
CollectionEquatable (dynamic, medium)
72+
total runs: 382 155
73+
total time: 2.0000 s
74+
average run: 5 μs
75+
runs/second: 200 000
76+
units: 100
77+
units/second: 20 000 000
78+
time per unit: 0.0500 μs
79+
80+
CollectionEquatable (dynamic, large)
81+
total runs: 390 713
82+
total time: 2.0000 s
83+
average run: 5 μs
84+
runs/second: 200 000
85+
units: 100
86+
units/second: 20 000 000
87+
time per unit: 0.0500 μs
88+
```
89+
90+
**AOT**
91+
92+
```
93+
EmptyEquatable
94+
total runs: 1 615 534
95+
total time: 2.0000 s
96+
average run: 1 μs
97+
runs/second: 1 000 000
98+
units: 100
99+
units/second: 100 000 000
100+
time per unit: 0.0100 μs
101+
102+
PrimitiveEquatable
103+
total runs: 928 013
104+
total time: 2.0000 s
105+
average run: 2 μs
106+
runs/second: 500 000
107+
units: 100
108+
units/second: 50 000 000
109+
time per unit: 0.0200 μs
110+
111+
CollectionEquatable (static, small)
112+
total runs: 128 224
113+
total time: 2.0000 s
114+
average run: 15 μs
115+
runs/second: 66 667
116+
units: 100
117+
units/second: 6 666 667
118+
time per unit: 0.1500 μs
119+
120+
CollectionEquatable (static, medium)
121+
total runs: 104 624
122+
total time: 2.0000 s
123+
average run: 19 μs
124+
runs/second: 52 632
125+
units: 100
126+
units/second: 5 263 158
127+
time per unit: 0.1900 μs
128+
129+
CollectionEquatable (static, large)
130+
total runs: 33 653
131+
total time: 2.0000 s
132+
average run: 59 μs
133+
runs/second: 16 949
134+
units: 100
135+
units/second: 1 694 915
136+
time per unit: 0.5900 μs
59137
60138
CollectionEquatable (dynamic, small)
61-
total runs: 400 934
139+
total runs: 483 177
62140
total time: 2.0000 s
63141
average run: 4 μs
64-
runs/second: 250 000
65-
units: 100
66-
units/second: 25 000 000
142+
runs/second: 250 000
143+
units: 100
144+
units/second: 25 000 000
67145
time per unit: 0.0400 μs
68146
69147
CollectionEquatable (dynamic, medium)
70-
total runs: 400 408
148+
total runs: 488 550
71149
total time: 2.0000 s
72150
average run: 4 μs
73-
runs/second: 250 000
74-
units: 100
75-
units/second: 25 000 000
151+
runs/second: 250 000
152+
units: 100
153+
units/second: 25 000 000
76154
time per unit: 0.0400 μs
77155
78156
CollectionEquatable (dynamic, large)
79-
total runs: 400 966
157+
total runs: 494 041
80158
total time: 2.0000 s
81159
average run: 4 μs
82-
runs/second: 250 000
83-
units: 100
84-
units/second: 25 000 000
160+
runs/second: 250 000
161+
units: 100
162+
units/second: 25 000 000
85163
time per unit: 0.0400 μs
86164
```
87165

88-
_Last Updated: June 3, 2024 using `725b76c9ef072695f3ae4f036c4fa5e015528f13`_
166+
_Last Updated: November 20, 2024 using `29e6c77a2e6b25e35cce66276bc2afeab1c805bd`_
89167

90168
_MacBook Pro (M1 Pro, 16GB RAM)_
91169

92-
Dart SDK version: 3.5.0-218.0.dev (dev) (Mon Jun 3 13:02:57 2024 -0700) on "macos_arm64"
170+
Dart SDK version: Dart SDK version: 3.5.4 (stable) (Wed Oct 16 16:18:51 2024 +0000) on "macos_arm64"

benchmarks/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: equatable_benchmarks
22
publish_to: none
33

44
environment:
5-
sdk: ">=2.12.0 <3.0.0"
5+
sdk: ">=3.5.0 <4.0.0"
66

77
dependencies:
88
equatable: ^2.0.0

lib/src/equatable.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ abstract class Equatable {
4646
return identical(this, other) ||
4747
other is Equatable &&
4848
runtimeType == other.runtimeType &&
49-
equals(props, other.props);
49+
iterableEquals(props, other.props);
5050
}
5151

5252
@override

lib/src/equatable_mixin.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mixin EquatableMixin {
2222
return identical(this, other) ||
2323
other is EquatableMixin &&
2424
runtimeType == other.runtimeType &&
25-
equals(props, other.props);
25+
iterableEquals(props, other.props);
2626
}
2727

2828
@override

lib/src/equatable_utils.dart

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,62 @@ int mapPropsToHashCode(Iterable<Object?>? props) {
66
return _finish(props == null ? 0 : props.fold(0, _combine));
77
}
88

9-
const DeepCollectionEquality _equality = DeepCollectionEquality();
9+
/// Determines whether two iterables are equal.
10+
@pragma('vm:prefer-inline')
11+
bool iterableEquals(Iterable<Object?> a, Iterable<Object?> b) {
12+
assert(
13+
a is! Set && b is! Set,
14+
"iterableEquals doesn't support Sets. Use setEquals instead.",
15+
);
16+
if (identical(a, b)) return true;
17+
if (a.length != b.length) return false;
18+
for (var i = 0; i < a.length; i++) {
19+
if (!objectsEquals(a.elementAt(i), b.elementAt(i))) return false;
20+
}
21+
return true;
22+
}
1023

11-
/// Determines whether [list1] and [list2] are equal.
12-
bool equals(List<Object?>? list1, List<Object?>? list2) {
13-
if (identical(list1, list2)) return true;
14-
if (list1 == null || list2 == null) return false;
15-
final length = list1.length;
16-
if (length != list2.length) return false;
24+
/// Determines whether two sets are equal.
25+
bool setEquals(Set<Object?> a, Set<Object?> b) {
26+
if (identical(a, b)) return true;
27+
if (a.length != b.length) return false;
28+
for (final element in a) {
29+
if (!b.any((e) => objectsEquals(element, e))) return false;
30+
}
31+
return true;
32+
}
1733

18-
for (var i = 0; i < length; i++) {
19-
final unit1 = list1[i];
20-
final unit2 = list2[i];
34+
/// Determines whether two maps are equal.
35+
bool mapEquals(Map<Object?, Object?> a, Map<Object?, Object?> b) {
36+
if (identical(a, b)) return true;
37+
if (a.length != b.length) return false;
38+
for (final key in a.keys) {
39+
if (!objectsEquals(a[key], b[key])) return false;
40+
}
41+
return true;
42+
}
2143

22-
if (_isEquatable(unit1) && _isEquatable(unit2)) {
23-
if (unit1 != unit2) return false;
24-
} else if (unit1 is Iterable || unit1 is Map) {
25-
if (!_equality.equals(unit1, unit2)) return false;
26-
} else if (unit1?.runtimeType != unit2?.runtimeType) {
27-
return false;
28-
} else if (unit1 != unit2) {
29-
return false;
30-
}
44+
/// Determines whether two objects are equal.
45+
@pragma('vm:prefer-inline')
46+
bool objectsEquals(Object? a, Object? b) {
47+
if (identical(a, b)) return true;
48+
if (_isEquatable(a) && _isEquatable(b)) {
49+
return a == b;
50+
} else if (a is Set && b is Set) {
51+
return setEquals(a, b);
52+
} else if (a is Iterable && b is Iterable) {
53+
return iterableEquals(a, b);
54+
} else if (a is Map && b is Map) {
55+
return mapEquals(a, b);
56+
} else if (a?.runtimeType != b?.runtimeType) {
57+
return false;
58+
} else if (a != b) {
59+
return false;
3160
}
3261
return true;
3362
}
3463

64+
@pragma('vm:prefer-inline')
3565
bool _isEquatable(Object? object) {
3666
return object is Equatable || object is EquatableMixin;
3767
}

0 commit comments

Comments
 (0)