From 31452f24aa713643c8bf0f6f511e9f474e3a49e7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 1 Feb 2024 00:50:26 +0100 Subject: [PATCH 1/6] ~30% deserialization speedup for long repeated fields by reducing boxing overhead. Also memoize storage lookup from previously deserialized field, though that seems to have only a minor impact since the lookup is fast already. --- protobuf/lib/src/protobuf/coded_buffer.dart | 170 ++++++++++++++------ 1 file changed, 122 insertions(+), 48 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index e54d869b2..fa183140a 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -33,8 +33,19 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, CodedBufferReader input, ExtensionRegistry registry) { ArgumentError.checkNotNull(registry); fs._ensureWritable(); + + // Micro-optimization: cache the storage lookup for repeated fields. + var prevTag = -1; + List? cachedList; + while (true) { final tag = input.readTag(); + // If the current field's tag is different from previous, invalidate cache. + if (tag != prevTag) { + cachedList = null; + prevTag = tag; + } + if (tag == 0) return; final wireType = tag & 0x7; final tagNumber = tag >> 3; @@ -128,63 +139,133 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_BOOL: - _readPackable(meta, fs, input, wireType, fi, input.readBool); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readBool())); + } else { + list.add(input.readBool()); + } break; case PbFieldType._REPEATED_BYTES: - fs._ensureRepeatedField(meta, fi).add(input.readBytes()); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + list.add(input.readBytes()); break; case PbFieldType._REPEATED_STRING: - fs._ensureRepeatedField(meta, fi).add(input.readString()); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + list.add(input.readString()); break; case PbFieldType._REPEATED_FLOAT: - _readPackable(meta, fs, input, wireType, fi, input.readFloat); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readFloat())); + } else { + list.add(input.readFloat()); + } break; case PbFieldType._REPEATED_DOUBLE: - _readPackable(meta, fs, input, wireType, fi, input.readDouble); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readDouble())); + } else { + list.add(input.readDouble()); + } break; case PbFieldType._REPEATED_ENUM: + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); _readPackableToListEnum( - meta, fs, input, wireType, fi, tagNumber, registry); + list, meta, fs, input, wireType, tagNumber, registry); break; case PbFieldType._REPEATED_GROUP: final subMessage = meta._makeEmptyMessage(tagNumber, registry); input.readGroup(tagNumber, subMessage, registry); - fs._ensureRepeatedField(meta, fi).add(subMessage); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + list.add(subMessage); break; case PbFieldType._REPEATED_INT32: - _readPackable(meta, fs, input, wireType, fi, input.readInt32); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readInt32())); + } else { + list.add(input.readInt32()); + } break; case PbFieldType._REPEATED_INT64: - _readPackable(meta, fs, input, wireType, fi, input.readInt64); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readInt64())); + } else { + list.add(input.readInt64()); + } break; case PbFieldType._REPEATED_SINT32: - _readPackable(meta, fs, input, wireType, fi, input.readSint32); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readSint32())); + } else { + list.add(input.readSint32()); + } break; case PbFieldType._REPEATED_SINT64: - _readPackable(meta, fs, input, wireType, fi, input.readSint64); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readSint32())); + } else { + list.add(input.readSint32()); + } break; case PbFieldType._REPEATED_UINT32: - _readPackable(meta, fs, input, wireType, fi, input.readUint32); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readUint32())); + } else { + list.add(input.readUint32()); + } break; case PbFieldType._REPEATED_UINT64: - _readPackable(meta, fs, input, wireType, fi, input.readUint64); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readUint64())); + } else { + list.add(input.readUint64()); + } break; case PbFieldType._REPEATED_FIXED32: - _readPackable(meta, fs, input, wireType, fi, input.readFixed32); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readFixed32())); + } else { + list.add(input.readFixed32()); + } break; case PbFieldType._REPEATED_FIXED64: - _readPackable(meta, fs, input, wireType, fi, input.readFixed64); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readFixed64())); + } else { + list.add(input.readFixed64()); + } break; case PbFieldType._REPEATED_SFIXED32: - _readPackable(meta, fs, input, wireType, fi, input.readSfixed32); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readSfixed32())); + } else { + list.add(input.readSfixed32()); + } break; case PbFieldType._REPEATED_SFIXED64: - _readPackable(meta, fs, input, wireType, fi, input.readSfixed64); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + if (wireType == WIRETYPE_LENGTH_DELIMITED) { + _readPacked(input, () => list.add(input.readSfixed64())); + } else { + list.add(input.readSfixed64()); + } break; case PbFieldType._REPEATED_MESSAGE: final subMessage = meta._makeEmptyMessage(tagNumber, registry); input.readMessage(subMessage, registry); - fs._ensureRepeatedField(meta, fi).add(subMessage); + final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + list.add(subMessage); break; case PbFieldType._MAP: final mapFieldInfo = fi as MapFieldInfo; @@ -199,52 +280,45 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } } -void _readPackable(BuilderInfo meta, _FieldSet fs, CodedBufferReader input, - int wireType, FieldInfo fi, Function() readFunc) { - void readToList(List list) => list.add(readFunc()); - _readPackableToList(meta, fs, input, wireType, fi, readToList); +void _readPacked(CodedBufferReader input, void Function() readFunc) { + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + readFunc(); + } + }); } void _readPackableToListEnum( + List list, BuilderInfo meta, _FieldSet fs, CodedBufferReader input, int wireType, - FieldInfo fi, int tagNumber, ExtensionRegistry registry) { - void readToList(List list) { - final rawValue = input.readEnum(); - final value = meta._decodeEnum(tagNumber, registry, rawValue); - if (value == null) { - final unknown = fs._ensureUnknownFields(); - unknown.mergeVarintField(tagNumber, Int64(rawValue)); - } else { - list.add(value); - } - } - - _readPackableToList(meta, fs, input, wireType, fi, readToList); -} - -void _readPackableToList( - BuilderInfo meta, - _FieldSet fs, - CodedBufferReader input, - int wireType, - FieldInfo fi, - Function(List) readToList) { - final list = fs._ensureRepeatedField(meta, fi); - if (wireType == WIRETYPE_LENGTH_DELIMITED) { // Packed. input._withLimit(input.readInt32(), () { while (!input.isAtEnd()) { - readToList(list); + final rawValue = input.readEnum(); + final value = meta._decodeEnum(tagNumber, registry, rawValue); + if (value == null) { + final unknown = fs._ensureUnknownFields(); + unknown.mergeVarintField(tagNumber, Int64(rawValue)); + } else { + list.add(value); + } } }); } else { // Not packed. - readToList(list); + final rawValue = input.readEnum(); + final value = meta._decodeEnum(tagNumber, registry, rawValue); + if (value == null) { + final unknown = fs._ensureUnknownFields(); + unknown.mergeVarintField(tagNumber, Int64(rawValue)); + } else { + list.add(value); + } } } From d5001a93d056fb3f52525543652ef5e8de46c0b5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 1 Feb 2024 13:43:20 +0100 Subject: [PATCH 2/6] Add a dedicated benchmark for protos with long repeated fields. --- benchmarks/bin/repeated_field.dart | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 benchmarks/bin/repeated_field.dart diff --git a/benchmarks/bin/repeated_field.dart b/benchmarks/bin/repeated_field.dart new file mode 100644 index 000000000..16c1b1780 --- /dev/null +++ b/benchmarks/bin/repeated_field.dart @@ -0,0 +1,45 @@ +import 'dart:typed_data'; + +import 'package:fixnum/fixnum.dart'; +import 'package:protobuf_benchmarks/benchmark_base.dart'; +import 'package:protobuf_benchmarks/generated/f12.pb.dart' as f12; +import 'package:protobuf_benchmarks/generated/google_message2.pb.dart'; + +class RepeatedBenchmark extends BenchmarkBase { + final Uint8List _buffer; + + RepeatedBenchmark(super.name, GoogleMessage2 message) + : _buffer = message.writeToBuffer(); + + @override + void run() => GoogleMessage2.fromBuffer(_buffer); +} + +class RepeatedEnumBenchmark extends BenchmarkBase { + final Uint8List _buffer; + + RepeatedEnumBenchmark(super.name, f12.A58 message) + : _buffer = message.writeToBuffer(); + + @override + void run() => f12.A58.fromBuffer(_buffer); +} + +void main() { + const kSize = 500000; + + RepeatedBenchmark( + 'repeated_int64', + GoogleMessage2(field130: List.generate(kSize, Int64.new)), + ).report(); + + RepeatedBenchmark( + 'repeated_string', + GoogleMessage2(field128: List.generate(kSize, (i) => i.toString())), + ).report(); + + RepeatedEnumBenchmark( + 'repeated_enum', + f12.A58(a306: List.generate(kSize, (_) => f12.A322.A324)), + ).report(); +} From 68bb70d28d9491989e0106645ed6adaae0b4f1f9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 1 Feb 2024 15:13:01 +0100 Subject: [PATCH 3/6] Drive-by nullability fix in BuilderInfo._decoceEnum. --- protobuf/lib/src/protobuf/builder_info.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/protobuf/lib/src/protobuf/builder_info.dart b/protobuf/lib/src/protobuf/builder_info.dart index 53ea7b2ca..1ae344133 100644 --- a/protobuf/lib/src/protobuf/builder_info.dart +++ b/protobuf/lib/src/protobuf/builder_info.dart @@ -323,10 +323,13 @@ class BuilderInfo { ProtobufEnum? _decodeEnum( int tagNumber, ExtensionRegistry? registry, int rawValue) { - var f = valueOfFunc(tagNumber); - if (f == null && registry != null) { - f = registry.getExtension(qualifiedMessageName, tagNumber)!.valueOf; + final f = valueOfFunc(tagNumber); + if (f != null) { + return f(rawValue); } - return f!(rawValue); + return registry + ?.getExtension(qualifiedMessageName, tagNumber) + ?.valueOf + ?.call(rawValue); } } From 1a95c01f72397f6f96702fff2b7b677a81dbc101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 22 Feb 2024 09:50:14 +0100 Subject: [PATCH 4/6] Fix sint64 case --- protobuf/lib/src/protobuf/coded_buffer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index fa183140a..5eb895477 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -208,9 +208,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SINT64: final list = cachedList ??= fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readSint32())); + _readPacked(input, () => list.add(input.readSint64())); } else { - list.add(input.readSint32()); + list.add(input.readSint64()); } break; case PbFieldType._REPEATED_UINT32: From 7ac5864dc1e2788f3eb13506657fc2433d52ee62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 22 Feb 2024 09:54:26 +0100 Subject: [PATCH 5/6] Remove duplication in enum code --- protobuf/lib/src/protobuf/coded_buffer.dart | 30 ++++++++++----------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index 5eb895477..99bb4ed48 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -300,25 +300,23 @@ void _readPackableToListEnum( // Packed. input._withLimit(input.readInt32(), () { while (!input.isAtEnd()) { - final rawValue = input.readEnum(); - final value = meta._decodeEnum(tagNumber, registry, rawValue); - if (value == null) { - final unknown = fs._ensureUnknownFields(); - unknown.mergeVarintField(tagNumber, Int64(rawValue)); - } else { - list.add(value); - } + _readRepeatedEnum(list, meta, fs, input, tagNumber, registry); } }); } else { // Not packed. - final rawValue = input.readEnum(); - final value = meta._decodeEnum(tagNumber, registry, rawValue); - if (value == null) { - final unknown = fs._ensureUnknownFields(); - unknown.mergeVarintField(tagNumber, Int64(rawValue)); - } else { - list.add(value); - } + _readRepeatedEnum(list, meta, fs, input, tagNumber, registry); + } +} + +void _readRepeatedEnum(List list, BuilderInfo meta, _FieldSet fs, + CodedBufferReader input, int tagNumber, ExtensionRegistry registry) { + final rawValue = input.readEnum(); + final value = meta._decodeEnum(tagNumber, registry, rawValue); + if (value == null) { + final unknown = fs._ensureUnknownFields(); + unknown.mergeVarintField(tagNumber, Int64(rawValue)); + } else { + list.add(value); } } From c5ecd3dbe4d4349c07adf940e5d1e53025a718b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 22 Feb 2024 09:56:05 +0100 Subject: [PATCH 6/6] Remove cached list It doesn't improve much in AOT, and slows down JS. --- protobuf/lib/src/protobuf/coded_buffer.dart | 47 ++++++++------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index 99bb4ed48..a102cbb1d 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -33,19 +33,8 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, CodedBufferReader input, ExtensionRegistry registry) { ArgumentError.checkNotNull(registry); fs._ensureWritable(); - - // Micro-optimization: cache the storage lookup for repeated fields. - var prevTag = -1; - List? cachedList; - while (true) { final tag = input.readTag(); - // If the current field's tag is different from previous, invalidate cache. - if (tag != prevTag) { - cachedList = null; - prevTag = tag; - } - if (tag == 0) return; final wireType = tag & 0x7; final tagNumber = tag >> 3; @@ -139,7 +128,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_BOOL: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readBool())); } else { @@ -147,15 +136,15 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_BYTES: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); list.add(input.readBytes()); break; case PbFieldType._REPEATED_STRING: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); list.add(input.readString()); break; case PbFieldType._REPEATED_FLOAT: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readFloat())); } else { @@ -163,7 +152,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_DOUBLE: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readDouble())); } else { @@ -171,18 +160,18 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_ENUM: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); _readPackableToListEnum( list, meta, fs, input, wireType, tagNumber, registry); break; case PbFieldType._REPEATED_GROUP: final subMessage = meta._makeEmptyMessage(tagNumber, registry); input.readGroup(tagNumber, subMessage, registry); - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); list.add(subMessage); break; case PbFieldType._REPEATED_INT32: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readInt32())); } else { @@ -190,7 +179,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_INT64: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readInt64())); } else { @@ -198,7 +187,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_SINT32: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readSint32())); } else { @@ -206,7 +195,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_SINT64: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readSint64())); } else { @@ -214,7 +203,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_UINT32: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readUint32())); } else { @@ -222,7 +211,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_UINT64: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readUint64())); } else { @@ -230,7 +219,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_FIXED32: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readFixed32())); } else { @@ -238,7 +227,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_FIXED64: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readFixed64())); } else { @@ -246,7 +235,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_SFIXED32: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readSfixed32())); } else { @@ -254,7 +243,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } break; case PbFieldType._REPEATED_SFIXED64: - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { _readPacked(input, () => list.add(input.readSfixed64())); } else { @@ -264,7 +253,7 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_MESSAGE: final subMessage = meta._makeEmptyMessage(tagNumber, registry); input.readMessage(subMessage, registry); - final list = cachedList ??= fs._ensureRepeatedField(meta, fi); + final list = fs._ensureRepeatedField(meta, fi); list.add(subMessage); break; case PbFieldType._MAP: