Skip to content

Commit ef0ab7d

Browse files
ignatzosa1
andauthored
Optimize repeated field decoding (#911)
Avoid allocating tear-off closures when decoding repeated but non-packed fields. Also refactors `BuilderInfo._decodeEnum`. # Benchmarks `repeated_int64`, `repeated_string`, and `repeated_enum` are the new benchmarks added with this PR. `fromBuffer` is an internal benchmark that decodes a large buffer. `fromBufferUnmodifiableInput` is the same as `fromBuffer`, but the input `Uint8List` is made unmodifiable. ## AOT | | Before | After | Diff | |-----------------------------|-----------|-----------|--------------------| | repeated_int64 | 84,549 us | 61,820 us | -22,729 us, -25.8% | | repeated_string | 67,183 us | 67,984 us | +801 us, +1.19% | | repeated_enum | 52,335 us | 44,772 us | -7,563 us, -14.4% | | fromBuffer | 25,661 us | 25,299 us | -362 us, -1.4% | | fromBufferUnmodifiableInput | 26,135 us | 25,689 us | -446 us, -1.7% | ## JS | | Before | After | Diff | |-----------------------------|------------|------------|--------------------| | repeated_int64 | 112,050 us | 109,850 us | -2,200 us, -1.9% | | repeated_string | 123,050 us | 126,650 us | +3,600 us, +2.9% | | repeated_enum | 43,920 us | 39,400 us | -4,520 us, -10.2% | | fromBuffer | 211,888 us | 168,250 us | -43,638 us, -20.5% | | fromBufferUnmodifiableInput | 227,444 us | 185,454 us | -41,990 us, -18.4% | --------- Co-authored-by: Ömer Sinan Ağacan <[email protected]>
1 parent f085bfd commit ef0ab7d

File tree

3 files changed

+161
-52
lines changed

3 files changed

+161
-52
lines changed

benchmarks/bin/repeated_field.dart

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:fixnum/fixnum.dart';
4+
import 'package:protobuf_benchmarks/benchmark_base.dart';
5+
import 'package:protobuf_benchmarks/generated/f12.pb.dart' as f12;
6+
import 'package:protobuf_benchmarks/generated/google_message2.pb.dart';
7+
8+
class RepeatedBenchmark extends BenchmarkBase {
9+
final Uint8List _buffer;
10+
11+
RepeatedBenchmark(super.name, GoogleMessage2 message)
12+
: _buffer = message.writeToBuffer();
13+
14+
@override
15+
void run() => GoogleMessage2.fromBuffer(_buffer);
16+
}
17+
18+
class RepeatedEnumBenchmark extends BenchmarkBase {
19+
final Uint8List _buffer;
20+
21+
RepeatedEnumBenchmark(super.name, f12.A58 message)
22+
: _buffer = message.writeToBuffer();
23+
24+
@override
25+
void run() => f12.A58.fromBuffer(_buffer);
26+
}
27+
28+
void main() {
29+
const kSize = 500000;
30+
31+
RepeatedBenchmark(
32+
'repeated_int64',
33+
GoogleMessage2(field130: List<Int64>.generate(kSize, Int64.new)),
34+
).report();
35+
36+
RepeatedBenchmark(
37+
'repeated_string',
38+
GoogleMessage2(field128: List<String>.generate(kSize, (i) => i.toString())),
39+
).report();
40+
41+
RepeatedEnumBenchmark(
42+
'repeated_enum',
43+
f12.A58(a306: List<f12.A322>.generate(kSize, (_) => f12.A322.A324)),
44+
).report();
45+
}

protobuf/lib/src/protobuf/builder_info.dart

+7-4
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,13 @@ class BuilderInfo {
323323

324324
ProtobufEnum? _decodeEnum(
325325
int tagNumber, ExtensionRegistry? registry, int rawValue) {
326-
var f = valueOfFunc(tagNumber);
327-
if (f == null && registry != null) {
328-
f = registry.getExtension(qualifiedMessageName, tagNumber)!.valueOf;
326+
final f = valueOfFunc(tagNumber);
327+
if (f != null) {
328+
return f(rawValue);
329329
}
330-
return f!(rawValue);
330+
return registry
331+
?.getExtension(qualifiedMessageName, tagNumber)
332+
?.valueOf
333+
?.call(rawValue);
331334
}
332335
}

protobuf/lib/src/protobuf/coded_buffer.dart

+109-48
Original file line numberDiff line numberDiff line change
@@ -128,63 +128,133 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs,
128128
}
129129
break;
130130
case PbFieldType._REPEATED_BOOL:
131-
_readPackable(meta, fs, input, wireType, fi, input.readBool);
131+
final list = fs._ensureRepeatedField(meta, fi);
132+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
133+
_readPacked(input, () => list.add(input.readBool()));
134+
} else {
135+
list.add(input.readBool());
136+
}
132137
break;
133138
case PbFieldType._REPEATED_BYTES:
134-
fs._ensureRepeatedField(meta, fi).add(input.readBytes());
139+
final list = fs._ensureRepeatedField(meta, fi);
140+
list.add(input.readBytes());
135141
break;
136142
case PbFieldType._REPEATED_STRING:
137-
fs._ensureRepeatedField(meta, fi).add(input.readString());
143+
final list = fs._ensureRepeatedField(meta, fi);
144+
list.add(input.readString());
138145
break;
139146
case PbFieldType._REPEATED_FLOAT:
140-
_readPackable(meta, fs, input, wireType, fi, input.readFloat);
147+
final list = fs._ensureRepeatedField(meta, fi);
148+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
149+
_readPacked(input, () => list.add(input.readFloat()));
150+
} else {
151+
list.add(input.readFloat());
152+
}
141153
break;
142154
case PbFieldType._REPEATED_DOUBLE:
143-
_readPackable(meta, fs, input, wireType, fi, input.readDouble);
155+
final list = fs._ensureRepeatedField(meta, fi);
156+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
157+
_readPacked(input, () => list.add(input.readDouble()));
158+
} else {
159+
list.add(input.readDouble());
160+
}
144161
break;
145162
case PbFieldType._REPEATED_ENUM:
163+
final list = fs._ensureRepeatedField(meta, fi);
146164
_readPackableToListEnum(
147-
meta, fs, input, wireType, fi, tagNumber, registry);
165+
list, meta, fs, input, wireType, tagNumber, registry);
148166
break;
149167
case PbFieldType._REPEATED_GROUP:
150168
final subMessage = meta._makeEmptyMessage(tagNumber, registry);
151169
input.readGroup(tagNumber, subMessage, registry);
152-
fs._ensureRepeatedField(meta, fi).add(subMessage);
170+
final list = fs._ensureRepeatedField(meta, fi);
171+
list.add(subMessage);
153172
break;
154173
case PbFieldType._REPEATED_INT32:
155-
_readPackable(meta, fs, input, wireType, fi, input.readInt32);
174+
final list = fs._ensureRepeatedField(meta, fi);
175+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
176+
_readPacked(input, () => list.add(input.readInt32()));
177+
} else {
178+
list.add(input.readInt32());
179+
}
156180
break;
157181
case PbFieldType._REPEATED_INT64:
158-
_readPackable(meta, fs, input, wireType, fi, input.readInt64);
182+
final list = fs._ensureRepeatedField(meta, fi);
183+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
184+
_readPacked(input, () => list.add(input.readInt64()));
185+
} else {
186+
list.add(input.readInt64());
187+
}
159188
break;
160189
case PbFieldType._REPEATED_SINT32:
161-
_readPackable(meta, fs, input, wireType, fi, input.readSint32);
190+
final list = fs._ensureRepeatedField(meta, fi);
191+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
192+
_readPacked(input, () => list.add(input.readSint32()));
193+
} else {
194+
list.add(input.readSint32());
195+
}
162196
break;
163197
case PbFieldType._REPEATED_SINT64:
164-
_readPackable(meta, fs, input, wireType, fi, input.readSint64);
198+
final list = fs._ensureRepeatedField(meta, fi);
199+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
200+
_readPacked(input, () => list.add(input.readSint64()));
201+
} else {
202+
list.add(input.readSint64());
203+
}
165204
break;
166205
case PbFieldType._REPEATED_UINT32:
167-
_readPackable(meta, fs, input, wireType, fi, input.readUint32);
206+
final list = fs._ensureRepeatedField(meta, fi);
207+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
208+
_readPacked(input, () => list.add(input.readUint32()));
209+
} else {
210+
list.add(input.readUint32());
211+
}
168212
break;
169213
case PbFieldType._REPEATED_UINT64:
170-
_readPackable(meta, fs, input, wireType, fi, input.readUint64);
214+
final list = fs._ensureRepeatedField(meta, fi);
215+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
216+
_readPacked(input, () => list.add(input.readUint64()));
217+
} else {
218+
list.add(input.readUint64());
219+
}
171220
break;
172221
case PbFieldType._REPEATED_FIXED32:
173-
_readPackable(meta, fs, input, wireType, fi, input.readFixed32);
222+
final list = fs._ensureRepeatedField(meta, fi);
223+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
224+
_readPacked(input, () => list.add(input.readFixed32()));
225+
} else {
226+
list.add(input.readFixed32());
227+
}
174228
break;
175229
case PbFieldType._REPEATED_FIXED64:
176-
_readPackable(meta, fs, input, wireType, fi, input.readFixed64);
230+
final list = fs._ensureRepeatedField(meta, fi);
231+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
232+
_readPacked(input, () => list.add(input.readFixed64()));
233+
} else {
234+
list.add(input.readFixed64());
235+
}
177236
break;
178237
case PbFieldType._REPEATED_SFIXED32:
179-
_readPackable(meta, fs, input, wireType, fi, input.readSfixed32);
238+
final list = fs._ensureRepeatedField(meta, fi);
239+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
240+
_readPacked(input, () => list.add(input.readSfixed32()));
241+
} else {
242+
list.add(input.readSfixed32());
243+
}
180244
break;
181245
case PbFieldType._REPEATED_SFIXED64:
182-
_readPackable(meta, fs, input, wireType, fi, input.readSfixed64);
246+
final list = fs._ensureRepeatedField(meta, fi);
247+
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
248+
_readPacked(input, () => list.add(input.readSfixed64()));
249+
} else {
250+
list.add(input.readSfixed64());
251+
}
183252
break;
184253
case PbFieldType._REPEATED_MESSAGE:
185254
final subMessage = meta._makeEmptyMessage(tagNumber, registry);
186255
input.readMessage(subMessage, registry);
187-
fs._ensureRepeatedField(meta, fi).add(subMessage);
256+
final list = fs._ensureRepeatedField(meta, fi);
257+
list.add(subMessage);
188258
break;
189259
case PbFieldType._MAP:
190260
final mapFieldInfo = fi as MapFieldInfo;
@@ -199,52 +269,43 @@ void _mergeFromCodedBufferReader(BuilderInfo meta, _FieldSet fs,
199269
}
200270
}
201271

202-
void _readPackable(BuilderInfo meta, _FieldSet fs, CodedBufferReader input,
203-
int wireType, FieldInfo fi, Function() readFunc) {
204-
void readToList(List list) => list.add(readFunc());
205-
_readPackableToList(meta, fs, input, wireType, fi, readToList);
272+
void _readPacked(CodedBufferReader input, void Function() readFunc) {
273+
input._withLimit(input.readInt32(), () {
274+
while (!input.isAtEnd()) {
275+
readFunc();
276+
}
277+
});
206278
}
207279

208280
void _readPackableToListEnum(
281+
List list,
209282
BuilderInfo meta,
210283
_FieldSet fs,
211284
CodedBufferReader input,
212285
int wireType,
213-
FieldInfo fi,
214286
int tagNumber,
215287
ExtensionRegistry registry) {
216-
void readToList(List list) {
217-
final rawValue = input.readEnum();
218-
final value = meta._decodeEnum(tagNumber, registry, rawValue);
219-
if (value == null) {
220-
final unknown = fs._ensureUnknownFields();
221-
unknown.mergeVarintField(tagNumber, Int64(rawValue));
222-
} else {
223-
list.add(value);
224-
}
225-
}
226-
227-
_readPackableToList(meta, fs, input, wireType, fi, readToList);
228-
}
229-
230-
void _readPackableToList(
231-
BuilderInfo meta,
232-
_FieldSet fs,
233-
CodedBufferReader input,
234-
int wireType,
235-
FieldInfo fi,
236-
Function(List) readToList) {
237-
final list = fs._ensureRepeatedField(meta, fi);
238-
239288
if (wireType == WIRETYPE_LENGTH_DELIMITED) {
240289
// Packed.
241290
input._withLimit(input.readInt32(), () {
242291
while (!input.isAtEnd()) {
243-
readToList(list);
292+
_readRepeatedEnum(list, meta, fs, input, tagNumber, registry);
244293
}
245294
});
246295
} else {
247296
// Not packed.
248-
readToList(list);
297+
_readRepeatedEnum(list, meta, fs, input, tagNumber, registry);
298+
}
299+
}
300+
301+
void _readRepeatedEnum(List list, BuilderInfo meta, _FieldSet fs,
302+
CodedBufferReader input, int tagNumber, ExtensionRegistry registry) {
303+
final rawValue = input.readEnum();
304+
final value = meta._decodeEnum(tagNumber, registry, rawValue);
305+
if (value == null) {
306+
final unknown = fs._ensureUnknownFields();
307+
unknown.mergeVarintField(tagNumber, Int64(rawValue));
308+
} else {
309+
list.add(value);
249310
}
250311
}

0 commit comments

Comments
 (0)