Skip to content

Commit

Permalink
perf: improve equality comparison performance (#173)
Browse files Browse the repository at this point in the history
Co-authored-by: Felix Angelov <[email protected]>
  • Loading branch information
Maksimka101 and felangel authored Nov 21, 2024
1 parent fc5cf81 commit a679c3d
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 60 deletions.
154 changes: 116 additions & 38 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,82 +11,160 @@ Benchmarks used to measure the performance of equality comparisons using `packag

## Results

**JIT**

```
EmptyEquatable
total runs: 2 064 037
total runs: 2 729 471
total time: 2.0000 s
average run: 0 μs
runs/second: Infinity
units: 100
units: 100
units/second: Infinity
time per unit: 0.0000 μs
PrimitiveEquatable
total runs: 729 555
total runs: 669 972
total time: 2.0000 s
average run: 2 μs
runs/second: 500 000
units: 100
units/second: 50 000 000
runs/second: 500 000
units: 100
units/second: 50 000 000
time per unit: 0.0200 μs
CollectionEquatable (static, small)
total runs: 51 944
total runs: 144 932
total time: 2.0000 s
average run: 38 μs
runs/second: 26 316
units: 100
units/second: 2 631 579
time per unit: 0.3800 μs
average run: 13 μs
runs/second: 76 923
units: 100
units/second: 7 692 308
time per unit: 0.1300 μs
CollectionEquatable (static, medium)
total runs: 44 572
total runs: 84 533
total time: 2.0000 s
average run: 44 μs
runs/second: 22 727
units: 100
units/second: 2 272 727
time per unit: 0.4400 μs
average run: 23 μs
runs/second: 43 478
units: 100
units/second: 4 347 826
time per unit: 0.2300 μs
CollectionEquatable (static, large)
total runs: 21 027
total runs: 16 457
total time: 2.0001 s
average run: 95 μs
runs/second: 10 526
units: 100
units/second: 1 052 632
time per unit: 0.9500 μs
average run: 121 μs
runs/second: 8 264.5
units: 100
units/second: 826 446
time per unit: 1.2100 μs
CollectionEquatable (dynamic, small)
total runs: 388 236
total time: 2.0000 s
average run: 5 μs
runs/second: 200 000
units: 100
units/second: 20 000 000
time per unit: 0.0500 μs
CollectionEquatable (dynamic, medium)
total runs: 382 155
total time: 2.0000 s
average run: 5 μs
runs/second: 200 000
units: 100
units/second: 20 000 000
time per unit: 0.0500 μs
CollectionEquatable (dynamic, large)
total runs: 390 713
total time: 2.0000 s
average run: 5 μs
runs/second: 200 000
units: 100
units/second: 20 000 000
time per unit: 0.0500 μs
```

**AOT**

```
EmptyEquatable
total runs: 1 615 534
total time: 2.0000 s
average run: 1 μs
runs/second: 1 000 000
units: 100
units/second: 100 000 000
time per unit: 0.0100 μs
PrimitiveEquatable
total runs: 928 013
total time: 2.0000 s
average run: 2 μs
runs/second: 500 000
units: 100
units/second: 50 000 000
time per unit: 0.0200 μs
CollectionEquatable (static, small)
total runs: 128 224
total time: 2.0000 s
average run: 15 μs
runs/second: 66 667
units: 100
units/second: 6 666 667
time per unit: 0.1500 μs
CollectionEquatable (static, medium)
total runs: 104 624
total time: 2.0000 s
average run: 19 μs
runs/second: 52 632
units: 100
units/second: 5 263 158
time per unit: 0.1900 μs
CollectionEquatable (static, large)
total runs: 33 653
total time: 2.0000 s
average run: 59 μs
runs/second: 16 949
units: 100
units/second: 1 694 915
time per unit: 0.5900 μs
CollectionEquatable (dynamic, small)
total runs: 400 934
total runs: 483 177
total time: 2.0000 s
average run: 4 μs
runs/second: 250 000
units: 100
units/second: 25 000 000
runs/second: 250 000
units: 100
units/second: 25 000 000
time per unit: 0.0400 μs
CollectionEquatable (dynamic, medium)
total runs: 400 408
total runs: 488 550
total time: 2.0000 s
average run: 4 μs
runs/second: 250 000
units: 100
units/second: 25 000 000
runs/second: 250 000
units: 100
units/second: 25 000 000
time per unit: 0.0400 μs
CollectionEquatable (dynamic, large)
total runs: 400 966
total runs: 494 041
total time: 2.0000 s
average run: 4 μs
runs/second: 250 000
units: 100
units/second: 25 000 000
runs/second: 250 000
units: 100
units/second: 25 000 000
time per unit: 0.0400 μs
```

_Last Updated: June 3, 2024 using `725b76c9ef072695f3ae4f036c4fa5e015528f13`_
_Last Updated: November 20, 2024 using `29e6c77a2e6b25e35cce66276bc2afeab1c805bd`_

_MacBook Pro (M1 Pro, 16GB RAM)_

Dart SDK version: 3.5.0-218.0.dev (dev) (Mon Jun 3 13:02:57 2024 -0700) on "macos_arm64"
Dart SDK version: Dart SDK version: 3.5.4 (stable) (Wed Oct 16 16:18:51 2024 +0000) on "macos_arm64"
2 changes: 1 addition & 1 deletion benchmarks/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: equatable_benchmarks
publish_to: none

environment:
sdk: ">=2.12.0 <3.0.0"
sdk: ">=3.5.0 <4.0.0"

dependencies:
equatable: ^2.0.0
Expand Down
2 changes: 1 addition & 1 deletion lib/src/equatable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ abstract class Equatable {
return identical(this, other) ||
other is Equatable &&
runtimeType == other.runtimeType &&
equals(props, other.props);
iterableEquals(props, other.props);
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/equatable_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mixin EquatableMixin {
return identical(this, other) ||
other is EquatableMixin &&
runtimeType == other.runtimeType &&
equals(props, other.props);
iterableEquals(props, other.props);
}

@override
Expand Down
68 changes: 49 additions & 19 deletions lib/src/equatable_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,62 @@ int mapPropsToHashCode(Iterable<Object?>? props) {
return _finish(props == null ? 0 : props.fold(0, _combine));
}

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

/// Determines whether [list1] and [list2] are equal.
bool equals(List<Object?>? list1, List<Object?>? list2) {
if (identical(list1, list2)) return true;
if (list1 == null || list2 == null) return false;
final length = list1.length;
if (length != list2.length) return false;
/// Determines whether two sets are equal.
bool setEquals(Set<Object?> a, Set<Object?> b) {
if (identical(a, b)) return true;
if (a.length != b.length) return false;
for (final element in a) {
if (!b.any((e) => objectsEquals(element, e))) return false;
}
return true;
}

for (var i = 0; i < length; i++) {
final unit1 = list1[i];
final unit2 = list2[i];
/// Determines whether two maps are equal.
bool mapEquals(Map<Object?, Object?> a, Map<Object?, Object?> b) {
if (identical(a, b)) return true;
if (a.length != b.length) return false;
for (final key in a.keys) {
if (!objectsEquals(a[key], b[key])) return false;
}
return true;
}

if (_isEquatable(unit1) && _isEquatable(unit2)) {
if (unit1 != unit2) return false;
} else if (unit1 is Iterable || unit1 is Map) {
if (!_equality.equals(unit1, unit2)) return false;
} else if (unit1?.runtimeType != unit2?.runtimeType) {
return false;
} else if (unit1 != unit2) {
return false;
}
/// Determines whether two objects are equal.
@pragma('vm:prefer-inline')
bool objectsEquals(Object? a, Object? b) {
if (identical(a, b)) return true;
if (_isEquatable(a) && _isEquatable(b)) {
return a == b;
} else if (a is Set && b is Set) {
return setEquals(a, b);
} else if (a is Iterable && b is Iterable) {
return iterableEquals(a, b);
} else if (a is Map && b is Map) {
return mapEquals(a, b);
} else if (a?.runtimeType != b?.runtimeType) {
return false;
} else if (a != b) {
return false;
}
return true;
}

@pragma('vm:prefer-inline')
bool _isEquatable(Object? object) {
return object is Equatable || object is EquatableMixin;
}
Expand Down
Loading

0 comments on commit a679c3d

Please sign in to comment.