Remove fold() closures from `FieldSet._hashCode`. (#554)
The fold() calls come with significant runtime cost, and make it more
difficult to hoist accesses to commonly used variables. Just rewriting
this pattern shows a significant performance improvement for both JS and
VM benchmarks.
Baseline
```
JS
HashCode(RunTime): 9833.333333333334 us.
HashCode(RunTime): 9891.625615763547 us.
HashCode(RunTime): 9607.655502392345 us.
HashCode(RunTime): 9661.835748792271 us.
HashCode(RunTime): 9765.853658536585 us.
VM
HashCode(RunTime): 4527.384615384615 us.
HashCode(RunTime): 4534.151583710407 us.
HashCode(RunTime): 4546.556818181818 us.
HashCode(RunTime): 4490.65470852017
```
Results
```
JS
HashCode(RunTime): 8004 us.
HashCode(RunTime): 7980.0796812749 us.
HashCode(RunTime): 7976.095617529881 us.
HashCode(RunTime): 7824.21875 us.
HashCode(RunTime): 7847.058823529412 us.
VM
HashCode(RunTime): 2474.5451174289246 us.
HashCode(RunTime): 2533.7037974683544 us.
HashCode(RunTime): 2532.4556962025317 us.
HashCode(RunTime): 2420.072551390568 us.
HashCode(RunTime): 2521.3198992443326
```
Co-authored-by: Loren Van Spronsen <lorenvs@google.com>
diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart
index 9800563..9e702b6 100644
--- a/protobuf/lib/src/protobuf/field_set.dart
+++ b/protobuf/lib/src/protobuf/field_set.dart
@@ -678,26 +678,27 @@
return hash;
}
- int hashEachField(int hash) {
- //non-extension fields
- hash = _infosSortedByTag.where((fi) => _values[fi.index!] != null).fold(
- hash, (int h, FieldInfo fi) => hashField(h, fi, _values[fi.index!]));
-
- if (!_hasExtensions) return hash;
-
- hash =
- _sorted(_extensions!._tagNumbers).fold(hash, (int h, int tagNumber) {
- var fi = _extensions!._getInfoOrNull(tagNumber)!;
- return hashField(h, fi, _extensions!._getFieldOrNull(fi));
- });
-
- return hash;
- }
-
// Hash with descriptor.
var hash = _HashUtils._combine(0, _meta.hashCode);
- // Hash with fields.
- hash = hashEachField(hash);
+
+ // Hash with non-extension fields.
+ final values = _values;
+ for (final fi in _infosSortedByTag) {
+ final value = values[fi.index!];
+ if (value == null) continue;
+ hash = hashField(hash, fi, value);
+ }
+
+ // Hash with extension fields.
+ if (_hasExtensions) {
+ final extensions = _extensions!;
+ final sortedByTagNumbers = _sorted(extensions._tagNumbers);
+ for (final tagNumber in sortedByTagNumbers) {
+ final fi = extensions._getInfoOrNull(tagNumber)!;
+ hash = hashField(hash, fi, extensions._getFieldOrNull(fi));
+ }
+ }
+
// Hash with unknown fields.
if (_hasUnknownFields) {
hash = _HashUtils._combine(hash, _unknownFields.hashCode);