From 1ed72f9181deccbe57b51852a00202eca9171766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Thu, 6 Feb 2025 13:52:40 +0100 Subject: [PATCH 01/16] Add pragmas --- protobuf/lib/src/protobuf/coded_buffer.dart | 55 ++++++++++++++----- .../lib/src/protobuf/coded_buffer_reader.dart | 31 +++++++++++ 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index a102cbb1d..94e0001c6 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -130,7 +130,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_BOOL: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readBool())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readBool()); + _readPacked(input, readElem); } else { list.add(input.readBool()); } @@ -146,7 +148,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_FLOAT: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readFloat())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readFloat()); + _readPacked(input, readElem); } else { list.add(input.readFloat()); } @@ -154,7 +158,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_DOUBLE: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readDouble())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readDouble()); + _readPacked(input, readElem); } else { list.add(input.readDouble()); } @@ -173,7 +179,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_INT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readInt32())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readInt32()); + _readPacked(input, readElem); } else { list.add(input.readInt32()); } @@ -181,7 +189,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_INT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readInt64())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readInt64()); + _readPacked(input, readElem); } else { list.add(input.readInt64()); } @@ -189,7 +199,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SINT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readSint32())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readSint32()); + _readPacked(input, readElem); } else { list.add(input.readSint32()); } @@ -197,7 +209,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SINT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readSint64())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readSint64()); + _readPacked(input, readElem); } else { list.add(input.readSint64()); } @@ -205,7 +219,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_UINT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readUint32())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readUint32()); + _readPacked(input, readElem); } else { list.add(input.readUint32()); } @@ -213,7 +229,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_UINT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readUint64())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readUint64()); + _readPacked(input, readElem); } else { list.add(input.readUint64()); } @@ -221,7 +239,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_FIXED32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readFixed32())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readFixed32()); + _readPacked(input, readElem); } else { list.add(input.readFixed32()); } @@ -229,7 +249,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_FIXED64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readFixed64())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readFixed64()); + _readPacked(input, readElem); } else { list.add(input.readFixed64()); } @@ -237,7 +259,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SFIXED32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readSfixed32())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readSfixed32()); + _readPacked(input, readElem); } else { list.add(input.readSfixed32()); } @@ -245,7 +269,9 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SFIXED64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - _readPacked(input, () => list.add(input.readSfixed64())); + @pragma('vm:prefer-inline') + void readElem() => list.add(input.readSfixed64()); + _readPacked(input, readElem); } else { list.add(input.readSfixed64()); } @@ -269,7 +295,8 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } } -void _readPacked(CodedBufferReader input, void Function() readFunc) { +@pragma('vm:prefer-inline') +void _readPacked(CodedBufferReader input, final void Function() readFunc) { input._withLimit(input.readInt32(), () { while (!input.isAtEnd()) { readFunc(); diff --git a/protobuf/lib/src/protobuf/coded_buffer_reader.dart b/protobuf/lib/src/protobuf/coded_buffer_reader.dart index cc7e3ddf1..80575ca79 100644 --- a/protobuf/lib/src/protobuf/coded_buffer_reader.dart +++ b/protobuf/lib/src/protobuf/coded_buffer_reader.dart @@ -42,14 +42,17 @@ class CodedBufferReader { throw InvalidProtocolBufferException.truncatedMessage(); } + @pragma('vm:prefer-inline') void checkLastTagWas(int value) { if (_lastTag != value) { throw InvalidProtocolBufferException.invalidEndTag(); } } + @pragma('vm:prefer-inline') bool isAtEnd() => _bufferPos >= _currentLimit; + @pragma('vm:prefer-inline') void _withLimit(int byteLimit, Function() callback) { if (byteLimit < 0) { throw ArgumentError( @@ -66,6 +69,7 @@ class CodedBufferReader { _currentLimit = oldLimit; } + @pragma('vm:prefer-inline') void _checkLimit(int increment) { assert(_currentLimit != -1); _bufferPos += increment; @@ -121,28 +125,45 @@ class CodedBufferReader { _currentLimit = oldLimit; } + @pragma('vm:prefer-inline') int readEnum() => readInt32(); + + @pragma('vm:prefer-inline') int readInt32() => _readRawVarint32(true); + + @pragma('vm:prefer-inline') Int64 readInt64() => _readRawVarint64(); + + @pragma('vm:prefer-inline') int readUint32() => _readRawVarint32(false); + + @pragma('vm:prefer-inline') Int64 readUint64() => _readRawVarint64(); + + @pragma('vm:prefer-inline') int readSint32() => _decodeZigZag32(readUint32()); + + @pragma('vm:prefer-inline') Int64 readSint64() => _decodeZigZag64(readUint64()); + @pragma('vm:prefer-inline') int readFixed32() { final pos = _bufferPos; _checkLimit(4); return _byteData.getUint32(pos, Endian.little); } + @pragma('vm:prefer-inline') Int64 readFixed64() => readSfixed64(); + @pragma('vm:prefer-inline') int readSfixed32() { final pos = _bufferPos; _checkLimit(4); return _byteData.getInt32(pos, Endian.little); } + @pragma('vm:prefer-inline') Int64 readSfixed64() { final pos = _bufferPos; _checkLimit(8); @@ -150,15 +171,18 @@ class CodedBufferReader { return Int64.fromBytes(view); } + @pragma('vm:prefer-inline') bool readBool() => _readRawVarint32(true) != 0; /// Read a length-delimited field as bytes. + @pragma('vm:prefer-inline') Uint8List readBytes() => Uint8List.fromList(readBytesAsView()); /// Read a length-delimited field as a view of the [CodedBufferReader]'s /// buffer. When storing the returned value directly (instead of e.g. parsing /// it as a UTF-8 string and copying) use [readBytes] instead to avoid /// holding on to the whole message, or copy the returned view. + @pragma('vm:prefer-inline') Uint8List readBytesAsView() { final length = readInt32(); _checkLimit(length); @@ -166,6 +190,7 @@ class CodedBufferReader { _buffer.buffer, _buffer.offsetInBytes + _bufferPos - length, length); } + @pragma('vm:prefer-inline') String readString() { final length = readInt32(); final stringPos = _bufferPos; @@ -174,18 +199,21 @@ class CodedBufferReader { .convert(_buffer, stringPos, stringPos + length); } + @pragma('vm:prefer-inline') double readFloat() { final pos = _bufferPos; _checkLimit(4); return _byteData.getFloat32(pos, Endian.little); } + @pragma('vm:prefer-inline') double readDouble() { final pos = _bufferPos; _checkLimit(8); return _byteData.getFloat64(pos, Endian.little); } + @pragma('vm:prefer-inline') int readTag() { if (isAtEnd()) { _lastTag = 0; @@ -228,6 +256,7 @@ class CodedBufferReader { } } + @pragma('vm:prefer-inline') static int _decodeZigZag32(int value) { if ((value & 0x1) == 1) { return -(value >> 1) - 1; @@ -236,11 +265,13 @@ class CodedBufferReader { } } + @pragma('vm:prefer-inline') static Int64 _decodeZigZag64(Int64 value) { if ((value & 0x1) == 1) value = -value; return value >> 1; } + @pragma('vm:prefer-inline') int _readRawVarintByte() { _checkLimit(1); return _buffer[_bufferPos - 1]; From 5073a40360d872fe48bc39d8aa5d765396b6409a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 10:12:08 +0100 Subject: [PATCH 02/16] Inline _readPacked manually --- protobuf/lib/src/protobuf/coded_buffer.dart | 113 +++++++++++--------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index 94e0001c6..ca5d26d9f 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -130,9 +130,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_BOOL: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readBool()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readBool()); + } + }); } else { list.add(input.readBool()); } @@ -148,9 +150,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_FLOAT: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readFloat()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readFloat()); + } + }); } else { list.add(input.readFloat()); } @@ -158,9 +162,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_DOUBLE: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readDouble()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readDouble()); + } + }); } else { list.add(input.readDouble()); } @@ -179,9 +185,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_INT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readInt32()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readInt32()); + } + }); } else { list.add(input.readInt32()); } @@ -189,9 +197,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_INT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readInt64()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readInt64()); + } + }); } else { list.add(input.readInt64()); } @@ -199,9 +209,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SINT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readSint32()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readSint32()); + } + }); } else { list.add(input.readSint32()); } @@ -209,9 +221,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SINT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readSint64()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readSint64()); + } + }); } else { list.add(input.readSint64()); } @@ -219,9 +233,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_UINT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readUint32()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readUint32()); + } + }); } else { list.add(input.readUint32()); } @@ -229,9 +245,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_UINT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readUint64()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readUint64()); + } + }); } else { list.add(input.readUint64()); } @@ -239,9 +257,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_FIXED32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readFixed32()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readFixed32()); + } + }); } else { list.add(input.readFixed32()); } @@ -249,9 +269,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_FIXED64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readFixed64()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readFixed64()); + } + }); } else { list.add(input.readFixed64()); } @@ -259,9 +281,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SFIXED32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readSfixed32()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readSfixed32()); + } + }); } else { list.add(input.readSfixed32()); } @@ -269,9 +293,11 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_SFIXED64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - @pragma('vm:prefer-inline') - void readElem() => list.add(input.readSfixed64()); - _readPacked(input, readElem); + input._withLimit(input.readInt32(), () { + while (!input.isAtEnd()) { + list.add(input.readSfixed64()); + } + }); } else { list.add(input.readSfixed64()); } @@ -295,15 +321,6 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, } } -@pragma('vm:prefer-inline') -void _readPacked(CodedBufferReader input, final void Function() readFunc) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - readFunc(); - } - }); -} - void _readPackableToListEnum( List list, BuilderInfo meta, From 8c41b3c9ad44a270623f05e8da8dbde4ce0d0d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 10:16:01 +0100 Subject: [PATCH 03/16] More inline pragmas --- protobuf/lib/src/protobuf/field_set.dart | 2 ++ protobuf/lib/src/protobuf/unknown_field_set.dart | 1 + 2 files changed, 3 insertions(+) diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart index 15c36586d..e5c5a2a65 100644 --- a/protobuf/lib/src/protobuf/field_set.dart +++ b/protobuf/lib/src/protobuf/field_set.dart @@ -4,6 +4,7 @@ part of '../../protobuf.dart'; +@pragma('vm:never-inline') void _throwFrozenMessageModificationError(String messageName, [String? methodName]) { if (methodName != null) { @@ -161,6 +162,7 @@ class _FieldSet { _unknownFields?._markReadOnly(); } + @pragma('vm:prefer-inline') void _ensureWritable() { if (_isReadOnly) { _throwFrozenMessageModificationError(_messageName); diff --git a/protobuf/lib/src/protobuf/unknown_field_set.dart b/protobuf/lib/src/protobuf/unknown_field_set.dart index aa4fe6c35..9fa33cb0c 100644 --- a/protobuf/lib/src/protobuf/unknown_field_set.dart +++ b/protobuf/lib/src/protobuf/unknown_field_set.dart @@ -190,6 +190,7 @@ class UnknownFieldSet { _isReadOnly = true; } + @pragma('vm:prefer-inline') void _ensureWritable(String methodName) { if (_isReadOnly) { _throwFrozenMessageModificationError('UnknownFieldSet', methodName); From edfe97a3e22258fb530571ef6ef16ae9ab6f1571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 10:18:23 +0100 Subject: [PATCH 04/16] Remove redundant checkNotNull calls from pre-null-safe days --- protobuf/lib/src/protobuf/coded_buffer.dart | 1 - protobuf/lib/src/protobuf/field_info.dart | 4 ---- protobuf/lib/src/protobuf/field_set.dart | 3 --- protobuf/lib/src/protobuf/generated_message.dart | 4 ---- 4 files changed, 12 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index a102cbb1d..1bcb864ff 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -31,7 +31,6 @@ void _writeToCodedBufferWriter(_FieldSet fs, CodedBufferWriter out) { void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, CodedBufferReader input, ExtensionRegistry registry) { - ArgumentError.checkNotNull(registry); fs._ensureWritable(); while (true) { final tag = input.readTag(); diff --git a/protobuf/lib/src/protobuf/field_info.dart b/protobuf/lib/src/protobuf/field_info.dart index dffce4a1a..fb245c8bf 100644 --- a/protobuf/lib/src/protobuf/field_info.dart +++ b/protobuf/lib/src/protobuf/field_info.dart @@ -132,8 +132,6 @@ class FieldInfo { {this.valueOf, this.enumValues, this.defaultEnumValue, String? protoName}) : makeDefault = (() => PbList(check: check!)), _protoName = protoName { - ArgumentError.checkNotNull(name, 'name'); - ArgumentError.checkNotNull(tagNumber, 'tagNumber'); assert(_isRepeated(type)); assert(check != null); assert(!_isEnum(type) || valueOf != null); @@ -280,8 +278,6 @@ class MapFieldInfo extends FieldInfo?> { defaultOrMaker: () => PbMap(keyFieldType, valueFieldType), defaultEnumValue: defaultEnumValue, protoName: protoName) { - ArgumentError.checkNotNull(name, 'name'); - ArgumentError.checkNotNull(tagNumber, 'tagNumber'); assert(_isMapField(type)); assert(!_isEnum(type) || valueOf != null); } diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart index 15c36586d..ea3dac841 100644 --- a/protobuf/lib/src/protobuf/field_set.dart +++ b/protobuf/lib/src/protobuf/field_set.dart @@ -258,8 +258,6 @@ class _FieldSet { /// Works for both extended and non-extended fields. /// Suitable for public API. void _setField(int tagNumber, Object value) { - ArgumentError.checkNotNull(value, 'value'); - final meta = _meta; final fi = _nonExtensionInfo(meta, tagNumber); if (fi == null) { @@ -284,7 +282,6 @@ class _FieldSet { /// Works for both extended and non-extended fields. /// Suitable for decoders that do their own validation. void _setFieldUnchecked(BuilderInfo meta, FieldInfo fi, value) { - ArgumentError.checkNotNull(fi, 'fi'); assert(!fi.isRepeated); if (fi.index == null) { _ensureExtensions() diff --git a/protobuf/lib/src/protobuf/generated_message.dart b/protobuf/lib/src/protobuf/generated_message.dart index 5a0b51cea..b16387e01 100644 --- a/protobuf/lib/src/protobuf/generated_message.dart +++ b/protobuf/lib/src/protobuf/generated_message.dart @@ -396,7 +396,6 @@ abstract class GeneratedMessage { /// Sets the value of a non-repeated extension field to [value]. void setExtension(Extension extension, Object value) { - ArgumentError.checkNotNull(value, 'value'); if (_isRepeated(extension.type)) { throw ArgumentError(_fieldSet._setFieldFailedMessage( extension, value, 'repeating field (use get + .add())')); @@ -489,7 +488,6 @@ abstract class GeneratedMessage { /// For generated code only. /// @nodoc void $_setFloat(int index, double value) { - ArgumentError.checkNotNull(value, 'value'); if (!_isFloat32(value)) { _fieldSet._$check(index, value); } @@ -503,7 +501,6 @@ abstract class GeneratedMessage { /// For generated code only. /// @nodoc void $_setSignedInt32(int index, int value) { - ArgumentError.checkNotNull(value, 'value'); if (!_isSigned32(value)) { _fieldSet._$check(index, value); } @@ -513,7 +510,6 @@ abstract class GeneratedMessage { /// For generated code only. /// @nodoc void $_setUnsignedInt32(int index, int value) { - ArgumentError.checkNotNull(value, 'value'); if (!_isUnsigned32(value)) { _fieldSet._$check(index, value); } From 69562f68f253848a8c6d88373437d8eb0bded63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 11:06:15 +0100 Subject: [PATCH 05/16] More tweaks --- protobuf/lib/src/protobuf/field_set.dart | 1 - protobuf/lib/src/protobuf/unknown_field_set.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart index ed1f35df1..5d5188052 100644 --- a/protobuf/lib/src/protobuf/field_set.dart +++ b/protobuf/lib/src/protobuf/field_set.dart @@ -162,7 +162,6 @@ class _FieldSet { _unknownFields?._markReadOnly(); } - @pragma('vm:prefer-inline') void _ensureWritable() { if (_isReadOnly) { _throwFrozenMessageModificationError(_messageName); diff --git a/protobuf/lib/src/protobuf/unknown_field_set.dart b/protobuf/lib/src/protobuf/unknown_field_set.dart index 9fa33cb0c..aa4fe6c35 100644 --- a/protobuf/lib/src/protobuf/unknown_field_set.dart +++ b/protobuf/lib/src/protobuf/unknown_field_set.dart @@ -190,7 +190,6 @@ class UnknownFieldSet { _isReadOnly = true; } - @pragma('vm:prefer-inline') void _ensureWritable(String methodName) { if (_isReadOnly) { _throwFrozenMessageModificationError('UnknownFieldSet', methodName); From 899badbe02c478a771129b7c8e996f83ff42262b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 12:00:48 +0100 Subject: [PATCH 06/16] More tweaks, not sure worth it --- protobuf/lib/src/protobuf/coded_buffer.dart | 230 ++++++++++++------ .../lib/src/protobuf/extension_field_set.dart | 1 + protobuf/lib/src/protobuf/pb_list.dart | 6 + 3 files changed, 157 insertions(+), 80 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer.dart b/protobuf/lib/src/protobuf/coded_buffer.dart index d49e4ee3b..463b22b5b 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -129,45 +129,65 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_BOOL: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readBool()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + // No need to check the element as for `bool` fields we only need to + // check that the value is not null, and we know in `add` below that + // the value isn't null (`readBool` doesn't return `null`). + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readBool()); + } + }); + } } else { - list.add(input.readBool()); + list._checkModifiable('add'); + list._addUnchecked(input.readBool()); } break; case PbFieldType._REPEATED_BYTES: final list = fs._ensureRepeatedField(meta, fi); - list.add(input.readBytes()); + list._checkModifiable('add'); + list._addUnchecked(input.readBytes()); break; case PbFieldType._REPEATED_STRING: final list = fs._ensureRepeatedField(meta, fi); - list.add(input.readString()); + list._checkModifiable('add'); + list._addUnchecked(input.readString()); break; case PbFieldType._REPEATED_FLOAT: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readFloat()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readFloat()); + } + }); + } } else { - list.add(input.readFloat()); + list._checkModifiable('add'); + list._addUnchecked(input.readFloat()); } break; case PbFieldType._REPEATED_DOUBLE: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readDouble()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readDouble()); + } + }); + } } else { - list.add(input.readDouble()); + list._checkModifiable('add'); + list._addUnchecked(input.readDouble()); } break; case PbFieldType._REPEATED_ENUM: @@ -184,121 +204,171 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, case PbFieldType._REPEATED_INT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readInt32()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readInt32()); + } + }); + } } else { - list.add(input.readInt32()); + list._checkModifiable('add'); + list._addUnchecked(input.readInt32()); } break; case PbFieldType._REPEATED_INT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readInt64()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readInt64()); + } + }); + } } else { - list.add(input.readInt64()); + list._checkModifiable('add'); + list._addUnchecked(input.readInt64()); } break; case PbFieldType._REPEATED_SINT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readSint32()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readSint32()); + } + }); + } } else { - list.add(input.readSint32()); + list._checkModifiable('add'); + list._addUnchecked(input.readSint32()); } break; case PbFieldType._REPEATED_SINT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readSint64()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readSint64()); + } + }); + } } else { - list.add(input.readSint64()); + list._checkModifiable('add'); + list._addUnchecked(input.readSint64()); } break; case PbFieldType._REPEATED_UINT32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readUint32()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readUint32()); + } + }); + } } else { - list.add(input.readUint32()); + list._checkModifiable('add'); + list._addUnchecked(input.readUint32()); } break; case PbFieldType._REPEATED_UINT64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readUint64()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readUint64()); + } + }); + } } else { - list.add(input.readUint64()); + list._checkModifiable('add'); + list._addUnchecked(input.readUint64()); } break; case PbFieldType._REPEATED_FIXED32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readFixed32()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readFixed32()); + } + }); + } } else { - list.add(input.readFixed32()); + list._checkModifiable('add'); + list._addUnchecked(input.readFixed32()); } break; case PbFieldType._REPEATED_FIXED64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readFixed64()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readFixed64()); + } + }); + } } else { - list.add(input.readFixed64()); + list._checkModifiable('add'); + list._addUnchecked(input.readFixed64()); } break; case PbFieldType._REPEATED_SFIXED32: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readSfixed32()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readSfixed32()); + } + }); + } } else { - list.add(input.readSfixed32()); + list._checkModifiable('add'); + list._addUnchecked(input.readSfixed32()); } break; case PbFieldType._REPEATED_SFIXED64: final list = fs._ensureRepeatedField(meta, fi); if (wireType == WIRETYPE_LENGTH_DELIMITED) { - input._withLimit(input.readInt32(), () { - while (!input.isAtEnd()) { - list.add(input.readSfixed64()); - } - }); + final limit = input.readInt32(); + if (limit != 0) { + list._checkModifiable('add'); + input._withLimit(limit, () { + while (!input.isAtEnd()) { + list._addUnchecked(input.readSfixed64()); + } + }); + } } else { - list.add(input.readSfixed64()); + list._checkModifiable('add'); + list._addUnchecked(input.readSfixed64()); } break; case PbFieldType._REPEATED_MESSAGE: diff --git a/protobuf/lib/src/protobuf/extension_field_set.dart b/protobuf/lib/src/protobuf/extension_field_set.dart index f90bb9096..28582dea6 100644 --- a/protobuf/lib/src/protobuf/extension_field_set.dart +++ b/protobuf/lib/src/protobuf/extension_field_set.dart @@ -66,6 +66,7 @@ class _ExtensionFieldSet { return newList; } + @pragma('vm:prefer-inline') dynamic _getFieldOrNull(Extension extension) => _values[extension.tagNumber]; void _clearFieldAndInfo(Extension fi) { diff --git a/protobuf/lib/src/protobuf/pb_list.dart b/protobuf/lib/src/protobuf/pb_list.dart index 792bfff4e..64b046581 100644 --- a/protobuf/lib/src/protobuf/pb_list.dart +++ b/protobuf/lib/src/protobuf/pb_list.dart @@ -52,6 +52,12 @@ class PbList extends ListBase { _wrappedList.add(element); } + @pragma('dart2js:tryInline') + @pragma('vm:prefer-inline') + void _addUnchecked(E element) { + _wrappedList.add(element); + } + @override @pragma('dart2js:never-inline') void addAll(Iterable iterable) { From dc6e8fb395b761f93704bc079d1d99c7649dab2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 13:24:44 +0100 Subject: [PATCH 07/16] Fix warnings --- protobuf/lib/src/protobuf/field_info.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/protobuf/lib/src/protobuf/field_info.dart b/protobuf/lib/src/protobuf/field_info.dart index fb245c8bf..356e7b63c 100644 --- a/protobuf/lib/src/protobuf/field_info.dart +++ b/protobuf/lib/src/protobuf/field_info.dart @@ -131,11 +131,10 @@ class FieldInfo { this.check, this.subBuilder, {this.valueOf, this.enumValues, this.defaultEnumValue, String? protoName}) : makeDefault = (() => PbList(check: check!)), - _protoName = protoName { - assert(_isRepeated(type)); - assert(check != null); - assert(!_isEnum(type) || valueOf != null); - } + _protoName = protoName, + assert(_isRepeated(type)), + assert(check != null), + assert(!_isEnum(type) || valueOf != null); static MakeDefaultFunc? findMakeDefault(int type, dynamic defaultOrMaker) { if (defaultOrMaker == null) return PbFieldType._defaultForType(type); @@ -274,11 +273,11 @@ class MapFieldInfo extends FieldInfo?> { this.valueCreator, {ProtobufEnum? defaultEnumValue, String? protoName}) - : super(name, tagNumber, index, type, + : assert(_isMapField(type)), + super(name, tagNumber, index, type, defaultOrMaker: () => PbMap(keyFieldType, valueFieldType), defaultEnumValue: defaultEnumValue, protoName: protoName) { - assert(_isMapField(type)); assert(!_isEnum(type) || valueOf != null); } From f39ddce0475b0659cb4e66b10e7c40a8222df56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 13:35:30 +0100 Subject: [PATCH 08/16] Fix formatting --- protobuf/lib/src/protobuf/coded_buffer_reader.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/protobuf/lib/src/protobuf/coded_buffer_reader.dart b/protobuf/lib/src/protobuf/coded_buffer_reader.dart index 80575ca79..c4c49ee2c 100644 --- a/protobuf/lib/src/protobuf/coded_buffer_reader.dart +++ b/protobuf/lib/src/protobuf/coded_buffer_reader.dart @@ -127,22 +127,22 @@ class CodedBufferReader { @pragma('vm:prefer-inline') int readEnum() => readInt32(); - + @pragma('vm:prefer-inline') int readInt32() => _readRawVarint32(true); - + @pragma('vm:prefer-inline') Int64 readInt64() => _readRawVarint64(); - + @pragma('vm:prefer-inline') int readUint32() => _readRawVarint32(false); - + @pragma('vm:prefer-inline') Int64 readUint64() => _readRawVarint64(); - + @pragma('vm:prefer-inline') int readSint32() => _decodeZigZag32(readUint32()); - + @pragma('vm:prefer-inline') Int64 readSint64() => _decodeZigZag64(readUint64()); From 23b884d660b03c0bf63c6c80325f5345f3111c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 10 Feb 2025 14:21:13 +0100 Subject: [PATCH 09/16] Make _mergeFromCodedBufferReader parameters final --- 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 463b22b5b..0ef638d50 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -29,8 +29,8 @@ void _writeToCodedBufferWriter(_FieldSet fs, CodedBufferWriter out) { } } -void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, - CodedBufferReader input, ExtensionRegistry registry) { +void _mergeFromCodedBufferReader(final BuilderInfo meta, final _FieldSet fs, + final CodedBufferReader input, final ExtensionRegistry registry) { fs._ensureWritable(); while (true) { final tag = input.readTag(); From f1d60d2246e504a932d1cdd34f443a77e1780056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Fri, 2 May 2025 14:18:13 +0100 Subject: [PATCH 10/16] Add benchmarks --- benchmarks/bin/binary_decode_packed.dart | 171 +++++++++++++++++++++++ benchmarks/protos/packed_fields.proto | 20 +++ benchmarks/tool/compile_protos.sh | 1 + 3 files changed, 192 insertions(+) create mode 100644 benchmarks/bin/binary_decode_packed.dart create mode 100644 benchmarks/protos/packed_fields.proto diff --git a/benchmarks/bin/binary_decode_packed.dart b/benchmarks/bin/binary_decode_packed.dart new file mode 100644 index 000000000..04d448577 --- /dev/null +++ b/benchmarks/bin/binary_decode_packed.dart @@ -0,0 +1,171 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:fixnum/fixnum.dart'; +import 'package:protobuf_benchmarks/benchmark_base.dart'; +import 'package:protobuf_benchmarks/generated/packed_fields.pb.dart'; + +PackedFields? sink = null; + +class PackedInt32DecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedInt32DecodingBenchmark() : super("PackedInt32Decoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + message.packedInt32.add(rand.nextInt(2147483647)); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedInt64DecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedInt64DecodingBenchmark() : super("PackedInt64Decoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + // Note: `Random` cannot generate more than the number below. + message.packedInt64.add(Int64(rand.nextInt(4294967296))); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedUint32DecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedUint32DecodingBenchmark() : super("PackedUint32Decoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + message.packedUint32.add(rand.nextInt(4294967295)); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedUint64DecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedUint64DecodingBenchmark() : super("PackedUint64Decoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + // Note: `Random` cannot generate more than the number below. + message.packedUint64.add(Int64(rand.nextInt(4294967296))); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedSint32DecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedSint32DecodingBenchmark() : super("PackedSint32Decoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + message.packedSint32.add(rand.nextInt(2147483647)); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedSint64DecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedSint64DecodingBenchmark() : super("PackedSint64Decoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + // Note: `Random` cannot generate more than the number below. + message.packedSint64.add(Int64(rand.nextInt(4294967296))); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedBoolDecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedBoolDecodingBenchmark() : super("PackedBoolDecoding") { + final rand = Random(123); + final message = PackedFields(); + for (int i = 0; i < 1000000; i += 1) { + message.packedBool.add(rand.nextBool()); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +class PackedEnumDecodingBenchmark extends BenchmarkBase { + late final Uint8List encoded; + + PackedEnumDecodingBenchmark() : super("PackedEnumDecoding") { + final rand = Random(123); + final message = PackedFields(); + final numEnums = Enum.values.length; + for (int i = 0; i < 1000000; i += 1) { + message.packedEnum.add(Enum.values[rand.nextInt(numEnums)]); + } + encoded = message.writeToBuffer(); + } + + @override + void run() { + sink = PackedFields()..mergeFromBuffer(encoded); + } +} + +void main() { + PackedInt32DecodingBenchmark().report(); + PackedInt64DecodingBenchmark().report(); + PackedUint32DecodingBenchmark().report(); + PackedUint64DecodingBenchmark().report(); + PackedSint32DecodingBenchmark().report(); + PackedSint64DecodingBenchmark().report(); + PackedBoolDecodingBenchmark().report(); + PackedEnumDecodingBenchmark().report(); +} diff --git a/benchmarks/protos/packed_fields.proto b/benchmarks/protos/packed_fields.proto new file mode 100644 index 000000000..2e4f94512 --- /dev/null +++ b/benchmarks/protos/packed_fields.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +message PackedFields { + repeated int32 packedInt32 = 1 [packed = true]; + repeated int64 packedInt64 = 2 [packed = true]; + repeated uint32 packedUint32 = 3 [packed = true]; + repeated uint64 packedUint64 = 4 [packed = true]; + repeated sint32 packedSint32 = 5 [packed = true]; + repeated sint64 packedSint64 = 6 [packed = true]; + repeated bool packedBool = 7 [packed = true]; + repeated Enum packedEnum = 8 [packed = true]; +} + +enum Enum { + ENUM_1 = 0; + ENUM_2 = 1; + ENUM_3 = 2; + ENUM_4 = 4; + ENUM_5 = 5; +} diff --git a/benchmarks/tool/compile_protos.sh b/benchmarks/tool/compile_protos.sh index 5fd12b833..729fe9b5e 100755 --- a/benchmarks/tool/compile_protos.sh +++ b/benchmarks/tool/compile_protos.sh @@ -12,6 +12,7 @@ SIMPLE_PROTOS=( "protos/google_message1_proto2.proto" "protos/google_message1_proto3.proto" "protos/google_message2.proto" + "protos/packed_fields.proto" ) set -x From c5925a79edeabb3ab4df9430821ac15aa99a80e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Fri, 2 May 2025 14:22:52 +0100 Subject: [PATCH 11/16] Fix warns --- benchmarks/bin/binary_decode_packed.dart | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/benchmarks/bin/binary_decode_packed.dart b/benchmarks/bin/binary_decode_packed.dart index 04d448577..848f4d959 100644 --- a/benchmarks/bin/binary_decode_packed.dart +++ b/benchmarks/bin/binary_decode_packed.dart @@ -9,15 +9,15 @@ import 'package:fixnum/fixnum.dart'; import 'package:protobuf_benchmarks/benchmark_base.dart'; import 'package:protobuf_benchmarks/generated/packed_fields.pb.dart'; -PackedFields? sink = null; +PackedFields? sink; class PackedInt32DecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedInt32DecodingBenchmark() : super("PackedInt32Decoding") { + PackedInt32DecodingBenchmark() : super('PackedInt32Decoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { message.packedInt32.add(rand.nextInt(2147483647)); } encoded = message.writeToBuffer(); @@ -32,10 +32,10 @@ class PackedInt32DecodingBenchmark extends BenchmarkBase { class PackedInt64DecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedInt64DecodingBenchmark() : super("PackedInt64Decoding") { + PackedInt64DecodingBenchmark() : super('PackedInt64Decoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { // Note: `Random` cannot generate more than the number below. message.packedInt64.add(Int64(rand.nextInt(4294967296))); } @@ -51,10 +51,10 @@ class PackedInt64DecodingBenchmark extends BenchmarkBase { class PackedUint32DecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedUint32DecodingBenchmark() : super("PackedUint32Decoding") { + PackedUint32DecodingBenchmark() : super('PackedUint32Decoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { message.packedUint32.add(rand.nextInt(4294967295)); } encoded = message.writeToBuffer(); @@ -69,10 +69,10 @@ class PackedUint32DecodingBenchmark extends BenchmarkBase { class PackedUint64DecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedUint64DecodingBenchmark() : super("PackedUint64Decoding") { + PackedUint64DecodingBenchmark() : super('PackedUint64Decoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { // Note: `Random` cannot generate more than the number below. message.packedUint64.add(Int64(rand.nextInt(4294967296))); } @@ -88,10 +88,10 @@ class PackedUint64DecodingBenchmark extends BenchmarkBase { class PackedSint32DecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedSint32DecodingBenchmark() : super("PackedSint32Decoding") { + PackedSint32DecodingBenchmark() : super('PackedSint32Decoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { message.packedSint32.add(rand.nextInt(2147483647)); } encoded = message.writeToBuffer(); @@ -106,10 +106,10 @@ class PackedSint32DecodingBenchmark extends BenchmarkBase { class PackedSint64DecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedSint64DecodingBenchmark() : super("PackedSint64Decoding") { + PackedSint64DecodingBenchmark() : super('PackedSint64Decoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { // Note: `Random` cannot generate more than the number below. message.packedSint64.add(Int64(rand.nextInt(4294967296))); } @@ -125,10 +125,10 @@ class PackedSint64DecodingBenchmark extends BenchmarkBase { class PackedBoolDecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedBoolDecodingBenchmark() : super("PackedBoolDecoding") { + PackedBoolDecodingBenchmark() : super('PackedBoolDecoding') { final rand = Random(123); final message = PackedFields(); - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { message.packedBool.add(rand.nextBool()); } encoded = message.writeToBuffer(); @@ -143,11 +143,11 @@ class PackedBoolDecodingBenchmark extends BenchmarkBase { class PackedEnumDecodingBenchmark extends BenchmarkBase { late final Uint8List encoded; - PackedEnumDecodingBenchmark() : super("PackedEnumDecoding") { + PackedEnumDecodingBenchmark() : super('PackedEnumDecoding') { final rand = Random(123); final message = PackedFields(); final numEnums = Enum.values.length; - for (int i = 0; i < 1000000; i += 1) { + for (var i = 0; i < 1000000; i += 1) { message.packedEnum.add(Enum.values[rand.nextInt(numEnums)]); } encoded = message.writeToBuffer(); From fa0a056ad713810362d33382ae4f3c8133c37630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Fri, 2 May 2025 14:29:18 +0100 Subject: [PATCH 12/16] Add inlines for Wasm --- .../lib/src/protobuf/coded_buffer_reader.dart | 25 +++++++++++++++++++ .../lib/src/protobuf/extension_field_set.dart | 1 + protobuf/lib/src/protobuf/pb_list.dart | 1 + 3 files changed, 27 insertions(+) diff --git a/protobuf/lib/src/protobuf/coded_buffer_reader.dart b/protobuf/lib/src/protobuf/coded_buffer_reader.dart index c4c49ee2c..e03e4c2c5 100644 --- a/protobuf/lib/src/protobuf/coded_buffer_reader.dart +++ b/protobuf/lib/src/protobuf/coded_buffer_reader.dart @@ -43,6 +43,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') void checkLastTagWas(int value) { if (_lastTag != value) { throw InvalidProtocolBufferException.invalidEndTag(); @@ -50,9 +51,11 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') bool isAtEnd() => _bufferPos >= _currentLimit; @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') void _withLimit(int byteLimit, Function() callback) { if (byteLimit < 0) { throw ArgumentError( @@ -70,6 +73,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') void _checkLimit(int increment) { assert(_currentLimit != -1); _bufferPos += increment; @@ -126,27 +130,35 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readEnum() => readInt32(); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readInt32() => _readRawVarint32(true); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Int64 readInt64() => _readRawVarint64(); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readUint32() => _readRawVarint32(false); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Int64 readUint64() => _readRawVarint64(); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readSint32() => _decodeZigZag32(readUint32()); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Int64 readSint64() => _decodeZigZag64(readUint64()); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readFixed32() { final pos = _bufferPos; _checkLimit(4); @@ -154,9 +166,11 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Int64 readFixed64() => readSfixed64(); @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readSfixed32() { final pos = _bufferPos; _checkLimit(4); @@ -164,6 +178,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Int64 readSfixed64() { final pos = _bufferPos; _checkLimit(8); @@ -172,10 +187,12 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') bool readBool() => _readRawVarint32(true) != 0; /// Read a length-delimited field as bytes. @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Uint8List readBytes() => Uint8List.fromList(readBytesAsView()); /// Read a length-delimited field as a view of the [CodedBufferReader]'s @@ -183,6 +200,7 @@ class CodedBufferReader { /// it as a UTF-8 string and copying) use [readBytes] instead to avoid /// holding on to the whole message, or copy the returned view. @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') Uint8List readBytesAsView() { final length = readInt32(); _checkLimit(length); @@ -191,6 +209,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') String readString() { final length = readInt32(); final stringPos = _bufferPos; @@ -200,6 +219,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') double readFloat() { final pos = _bufferPos; _checkLimit(4); @@ -207,6 +227,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') double readDouble() { final pos = _bufferPos; _checkLimit(8); @@ -214,6 +235,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int readTag() { if (isAtEnd()) { _lastTag = 0; @@ -257,6 +279,7 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') static int _decodeZigZag32(int value) { if ((value & 0x1) == 1) { return -(value >> 1) - 1; @@ -266,12 +289,14 @@ class CodedBufferReader { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') static Int64 _decodeZigZag64(Int64 value) { if ((value & 0x1) == 1) value = -value; return value >> 1; } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') int _readRawVarintByte() { _checkLimit(1); return _buffer[_bufferPos - 1]; diff --git a/protobuf/lib/src/protobuf/extension_field_set.dart b/protobuf/lib/src/protobuf/extension_field_set.dart index 28582dea6..9e9b5b010 100644 --- a/protobuf/lib/src/protobuf/extension_field_set.dart +++ b/protobuf/lib/src/protobuf/extension_field_set.dart @@ -67,6 +67,7 @@ class _ExtensionFieldSet { } @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') dynamic _getFieldOrNull(Extension extension) => _values[extension.tagNumber]; void _clearFieldAndInfo(Extension fi) { diff --git a/protobuf/lib/src/protobuf/pb_list.dart b/protobuf/lib/src/protobuf/pb_list.dart index 64b046581..0e0ccc1de 100644 --- a/protobuf/lib/src/protobuf/pb_list.dart +++ b/protobuf/lib/src/protobuf/pb_list.dart @@ -54,6 +54,7 @@ class PbList extends ListBase { @pragma('dart2js:tryInline') @pragma('vm:prefer-inline') + @pragma('wasm:prefer-inline') void _addUnchecked(E element) { _wrappedList.add(element); } From addc0e0b69890d060708b9bd61116af920fef442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Fri, 2 May 2025 14:30:57 +0100 Subject: [PATCH 13/16] Add a never-inline --- protobuf/lib/src/protobuf/field_set.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart index 5d5188052..780a8fe7b 100644 --- a/protobuf/lib/src/protobuf/field_set.dart +++ b/protobuf/lib/src/protobuf/field_set.dart @@ -5,6 +5,7 @@ part of '../../protobuf.dart'; @pragma('vm:never-inline') +@pragma('wasm:never-inline') void _throwFrozenMessageModificationError(String messageName, [String? methodName]) { if (methodName != null) { From 2dc80da8c775aa0ae7bfd0e63c51095efc18e788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Tue, 6 May 2025 11:18:43 +0100 Subject: [PATCH 14/16] Remove `final`s --- 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 0ef638d50..463b22b5b 100644 --- a/protobuf/lib/src/protobuf/coded_buffer.dart +++ b/protobuf/lib/src/protobuf/coded_buffer.dart @@ -29,8 +29,8 @@ void _writeToCodedBufferWriter(_FieldSet fs, CodedBufferWriter out) { } } -void _mergeFromCodedBufferReader(final BuilderInfo meta, final _FieldSet fs, - final CodedBufferReader input, final ExtensionRegistry registry) { +void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs, + CodedBufferReader input, ExtensionRegistry registry) { fs._ensureWritable(); while (true) { final tag = input.readTag(); From 5df34444a66b75366000e313fb27512458361e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Tue, 6 May 2025 11:24:31 +0100 Subject: [PATCH 15/16] Keep sink alive --- benchmarks/bin/binary_decode_packed.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmarks/bin/binary_decode_packed.dart b/benchmarks/bin/binary_decode_packed.dart index 848f4d959..5e4d3e359 100644 --- a/benchmarks/bin/binary_decode_packed.dart +++ b/benchmarks/bin/binary_decode_packed.dart @@ -168,4 +168,6 @@ void main() { PackedSint64DecodingBenchmark().report(); PackedBoolDecodingBenchmark().report(); PackedEnumDecodingBenchmark().report(); + + if (int.parse('1') == 0) print(sink); } From 086c55ccdbf230973d1db2a43dce3230ad9a0c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20A=C4=9Facan?= Date: Tue, 6 May 2025 17:02:47 +0100 Subject: [PATCH 16/16] Update changelog, package version --- protobuf/CHANGELOG.md | 6 ++++++ protobuf/pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md index b0bdcb1ac..e06317ac1 100644 --- a/protobuf/CHANGELOG.md +++ b/protobuf/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.0.1-wip + +* Improve packed field decoding performance. ([#959]) + +[#959]: https://github.com/google/protobuf.dart/pull/959 + ## 4.0.0 * **Breaking:** The following types and members are now removed: diff --git a/protobuf/pubspec.yaml b/protobuf/pubspec.yaml index af09c1a4b..043f8bad0 100644 --- a/protobuf/pubspec.yaml +++ b/protobuf/pubspec.yaml @@ -1,5 +1,5 @@ name: protobuf -version: 4.0.0 +version: 4.0.1-wip description: >- Runtime library for protocol buffers support. Use with package:protoc_plugin to generate Dart code for your '.proto' files.