Skip to content

Commit

Permalink
Split up GeneratedMessage for better performance.
Browse files Browse the repository at this point in the history
The _FieldSet and _ExtensionFieldSet classes now
contain all the fields. These classes are entirely
private to ensure that functions and methods that
use them are monomorphic.

_FieldSet stores non-extension field values in a
fixed-length list rather than a map.
FieldInfo.index points to the corresponding array
item, which is null if the field isn't present.

Also moved json and binary encode/decode methods
to separate files.

Also ran dartfmt on GeneratedMessage. Since so much
has changed, the diffs aren't going to be readable
anyway.

Possibly breaking changes:

- The FieldInfo constructors take one more argument.
But I don't see any usages outside the protobuf
library.

- When serializing a message as binary data
and the message contains extension fields,
the fields are no longer sorted by tag number.
According to the specification, protobuf decoders
should handle this correctly, but the performance
might not be the same.

- When adding a value to an extension field
that's a repeated list, if the list already
exists and the extension differs from its
previous value, the old extension is kept.
(The new one doesn't overwrite it.)

Performance:

JSON deserialization is about 2x faster (dart2js).
hasField is a bit slower when the field isn't present.

Decode JSON eagerly

Improves dart2js performance by 5% to 20%.
Reduces Dart VM performance by 5%.

BUG=
[email protected]

Review URL: https://chromiumcodereview.appspot.com//1361923002.
  • Loading branch information
Brian Slesinsky committed Sep 24, 2015
1 parent 4a7da4a commit bdaca27
Show file tree
Hide file tree
Showing 11 changed files with 1,144 additions and 785 deletions.
4 changes: 4 additions & 0 deletions lib/protobuf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ import 'dart:typed_data' show TypedData, Uint8List, ByteData, Endianness;
import 'package:crypto/crypto.dart' show CryptoUtils;
import 'package:fixnum/fixnum.dart' show Int64;

part 'src/protobuf/coded_buffer.dart';
part 'src/protobuf/coded_buffer_reader.dart';
part 'src/protobuf/coded_buffer_writer.dart';
part 'src/protobuf/builder_info.dart';
part 'src/protobuf/event_plugin.dart';
part 'src/protobuf/exceptions.dart';
part 'src/protobuf/extension.dart';
part 'src/protobuf/extension_field_set.dart';
part 'src/protobuf/extension_registry.dart';
part 'src/protobuf/field_error.dart';
part 'src/protobuf/field_info.dart';
part 'src/protobuf/field_set.dart';
part 'src/protobuf/field_type.dart';
part 'src/protobuf/generated_message.dart';
part 'src/protobuf/generated_service.dart';
part 'src/protobuf/json.dart';
part 'src/protobuf/pb_list.dart';
part 'src/protobuf/protobuf_enum.dart';
part 'src/protobuf/readonly_message.dart';
Expand Down
36 changes: 34 additions & 2 deletions lib/src/protobuf/builder_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,26 @@ class BuilderInfo {
final Map<String, FieldInfo> byName = <String, FieldInfo>{};
bool hasExtensions = false;
bool hasRequiredFields = true;
List _sortedByTag;

BuilderInfo(this.messageName);

void add(int tagNumber, String name, int fieldType,
dynamic defaultOrMaker,
CreateBuilderFunc subBuilder,
ValueOfFunc valueOf) {
var index = fieldInfo.length;
addField(new FieldInfo(
name, tagNumber, fieldType, defaultOrMaker, subBuilder, valueOf));
name, tagNumber, index, fieldType, defaultOrMaker, subBuilder, valueOf));
}

void addRepeated(int tagNumber, String name, int fieldType,
CheckFunc check,
CreateBuilderFunc subBuilder,
ValueOfFunc valueOf) {
var index = fieldInfo.length;
addField(new FieldInfo.repeated(
name, tagNumber, fieldType, check, subBuilder, valueOf));
name, tagNumber, index, fieldType, check, subBuilder, valueOf));
}

void addField(FieldInfo fi) {
Expand Down Expand Up @@ -113,4 +116,33 @@ class BuilderInfo {
FieldInfo i = fieldInfo[tagNumber];
return i != null ? i.valueOf : null;
}

/// Returns the FieldInfo for each field in tag number order.
List<FieldInfo> get sortedByTag {
if (_sortedByTag != null) return _sortedByTag;
// TODO(skybrian): perhaps the code generator should insert the FieldInfos
// in tag number order, to avoid sorting them?
_sortedByTag = new List<FieldInfo>.from(fieldInfo.values)
..sort((a, b) => a.tagNumber.compareTo(b.tagNumber));
return _sortedByTag;
}

GeneratedMessage _makeEmptyMessage(
int tagNumber, ExtensionRegistry extensionRegistry) {
CreateBuilderFunc subBuilderFunc = subBuilder(tagNumber);
if (subBuilderFunc == null && extensionRegistry != null) {
subBuilderFunc = extensionRegistry.getExtension(messageName,
tagNumber).subBuilder;
}
return subBuilderFunc();
}

_decodeEnum(int tagNumber, ExtensionRegistry registry, int rawValue) {

ValueOfFunc f = valueOfFunc(tagNumber);
if (f == null && registry != null) {
f = registry.getExtension(messageName, tagNumber).valueOf;
}
return f(rawValue);
}
}
224 changes: 224 additions & 0 deletions lib/src/protobuf/coded_buffer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright (c) 2015, 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.

part of protobuf;

void _writeToCodedBufferWriter(_FieldSet fs, CodedBufferWriter out) {
// Sorting by tag number isn't required, but it sometimes enables
// performance optimizations for the receiver. See:
// https://developers.google.com/protocol-buffers/docs/encoding?hl=en#order

for (var fi in fs._infosSortedByTag) {
var value = fs._values[fi.index];
if (value == null) continue;
out.writeField(fi.tagNumber, fi.type, value);
}

if (fs._hasExtensions) {
for (var tagNumber in sorted(fs._extensions._tagNumbers)) {
var fi = fs._extensions._getInfoOrNull(tagNumber);
out.writeField(tagNumber, fi.type, fs._extensions._getFieldOrNull(fi));
}
}
if (fs.hasUnknownFields) {
fs._unknownFields.writeToCodedBufferWriter(out);
}
}

void _mergeFromCodedBufferReader(
_FieldSet fs, CodedBufferReader input, ExtensionRegistry registry) {
assert(registry != null);

void readPackableToList(int wireType, FieldInfo fi, Function readToList) {
List list = fs._ensureRepeatedField(fi);

if (wireType == WIRETYPE_LENGTH_DELIMITED) {
// Packed.
input._withLimit(input.readInt32(), () {
while (!input.isAtEnd()) {
readToList(list);
}
});
} else {
// Not packed.
readToList(list);
}
}

void readPackable(int wireType, FieldInfo fi, Function readFunc) {
void readToList(List list) => list.add(readFunc());
readPackableToList(wireType, fi, readToList);
}

while (true) {
int tag = input.readTag();
if (tag == 0) return;
int wireType = tag & 0x7;
int tagNumber = tag >> 3;

FieldInfo fi = fs._nonExtensionInfo(tagNumber);
if (fi == null) {
fi = registry.getExtension(fs._messageName, tagNumber);
}

if (fi == null || !_wireTypeMatches(fi.type, wireType)) {
if (!fs._ensureUnknownFields().mergeFieldFromBuffer(tag, input)) {
return;
}
continue;
}

// Ignore required/optional packed/unpacked.
int fieldType = fi.type;
fieldType &= ~(PbFieldType._PACKED_BIT | PbFieldType._REQUIRED_BIT);
switch (fieldType) {
case PbFieldType._OPTIONAL_BOOL:
fs._setFieldUnchecked(fi, input.readBool());
break;
case PbFieldType._OPTIONAL_BYTES:
fs._setFieldUnchecked(fi, input.readBytes());
break;
case PbFieldType._OPTIONAL_STRING:
fs._setFieldUnchecked(fi, input.readString());
break;
case PbFieldType._OPTIONAL_FLOAT:
fs._setFieldUnchecked(fi, input.readFloat());
break;
case PbFieldType._OPTIONAL_DOUBLE:
fs._setFieldUnchecked(fi, input.readDouble());
break;
case PbFieldType._OPTIONAL_ENUM:
int rawValue = input.readEnum();
var value = fs._meta._decodeEnum(tagNumber, registry, rawValue);
if (value == null) {
var unknown = fs._ensureUnknownFields();
unknown.mergeVarintField(tagNumber, new Int64(rawValue));
} else {
fs._setFieldUnchecked(fi, value);
}
break;
case PbFieldType._OPTIONAL_GROUP:
GeneratedMessage subMessage =
fs._meta._makeEmptyMessage(tagNumber, registry);
var oldValue = fs._getFieldOrNull(fi);
if (oldValue != null) {
subMessage.mergeFromMessage(oldValue);
}
input.readGroup(tagNumber, subMessage, registry);
fs._setFieldUnchecked(fi, subMessage);
break;
case PbFieldType._OPTIONAL_INT32:
fs._setFieldUnchecked(fi, input.readInt32());
break;
case PbFieldType._OPTIONAL_INT64:
fs._setFieldUnchecked(fi, input.readInt64());
break;
case PbFieldType._OPTIONAL_SINT32:
fs._setFieldUnchecked(fi, input.readSint32());
break;
case PbFieldType._OPTIONAL_SINT64:
fs._setFieldUnchecked(fi, input.readSint64());
break;
case PbFieldType._OPTIONAL_UINT32:
fs._setFieldUnchecked(fi, input.readUint32());
break;
case PbFieldType._OPTIONAL_UINT64:
fs._setFieldUnchecked(fi, input.readUint64());
break;
case PbFieldType._OPTIONAL_FIXED32:
fs._setFieldUnchecked(fi, input.readFixed32());
break;
case PbFieldType._OPTIONAL_FIXED64:
fs._setFieldUnchecked(fi, input.readFixed64());
break;
case PbFieldType._OPTIONAL_SFIXED32:
fs._setFieldUnchecked(fi, input.readSfixed32());
break;
case PbFieldType._OPTIONAL_SFIXED64:
fs._setFieldUnchecked(fi, input.readSfixed64());
break;
case PbFieldType._OPTIONAL_MESSAGE:
GeneratedMessage subMessage =
fs._meta._makeEmptyMessage(tagNumber, registry);
var oldValue = fs._getFieldOrNull(fi);
if (oldValue != null) {
subMessage.mergeFromMessage(oldValue);
}
input.readMessage(subMessage, registry);
fs._setFieldUnchecked(fi, subMessage);
break;
case PbFieldType._REPEATED_BOOL:
readPackable(wireType, fi, input.readBool);
break;
case PbFieldType._REPEATED_BYTES:
fs._ensureRepeatedField(fi).add(input.readBytes());
break;
case PbFieldType._REPEATED_STRING:
fs._ensureRepeatedField(fi).add(input.readString());
break;
case PbFieldType._REPEATED_FLOAT:
readPackable(wireType, fi, input.readFloat);
break;
case PbFieldType._REPEATED_DOUBLE:
readPackable(wireType, fi, input.readDouble);
break;
case PbFieldType._REPEATED_ENUM:
readPackableToList(wireType, fi, (List list) {
int rawValue = input.readEnum();
var value = fs._meta._decodeEnum(tagNumber, registry, rawValue);
if (value == null) {
var unknown = fs._ensureUnknownFields();
unknown.mergeVarintField(tagNumber, new Int64(rawValue));
} else {
list.add(value);
}
});
break;
case PbFieldType._REPEATED_GROUP:
GeneratedMessage subMessage =
fs._meta._makeEmptyMessage(tagNumber, registry);
input.readGroup(tagNumber, subMessage, registry);
fs._ensureRepeatedField(fi).add(subMessage);
break;
case PbFieldType._REPEATED_INT32:
readPackable(wireType, fi, input.readInt32);
break;
case PbFieldType._REPEATED_INT64:
readPackable(wireType, fi, input.readInt64);
break;
case PbFieldType._REPEATED_SINT32:
readPackable(wireType, fi, input.readSint32);
break;
case PbFieldType._REPEATED_SINT64:
readPackable(wireType, fi, input.readSint64);
break;
case PbFieldType._REPEATED_UINT32:
readPackable(wireType, fi, input.readUint32);
break;
case PbFieldType._REPEATED_UINT64:
readPackable(wireType, fi, input.readUint64);
break;
case PbFieldType._REPEATED_FIXED32:
readPackable(wireType, fi, input.readFixed32);
break;
case PbFieldType._REPEATED_FIXED64:
readPackable(wireType, fi, input.readFixed64);
break;
case PbFieldType._REPEATED_SFIXED32:
readPackable(wireType, fi, input.readSfixed32);
break;
case PbFieldType._REPEATED_SFIXED64:
readPackable(wireType, fi, input.readSfixed64);
break;
case PbFieldType._REPEATED_MESSAGE:
GeneratedMessage subMessage =
fs._meta._makeEmptyMessage(tagNumber, registry);
input.readMessage(subMessage, registry);
fs._ensureRepeatedField(fi).add(subMessage);
break;
default:
throw 'Unknown field type $fieldType';
}
}
}
22 changes: 11 additions & 11 deletions lib/src/protobuf/extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ part of protobuf;
* An object representing an extension field.
*/
class Extension extends FieldInfo {

final String extendee;

Extension(this.extendee, String name, int tagNumber, int fieldType,
[dynamic defaultOrMaker,
CreateBuilderFunc subBuilder,
ValueOfFunc valueOf]) :
super(name, tagNumber, fieldType, defaultOrMaker, subBuilder, valueOf);

Extension.repeated(this.extendee, String name, int tagNumber, int fieldType,
CheckFunc check,
[CreateBuilderFunc subBuilder,
ValueOfFunc valueOf]) :
super.repeated(name, tagNumber, fieldType, check, subBuilder, valueOf);
[dynamic defaultOrMaker,
CreateBuilderFunc subBuilder,
ValueOfFunc valueOf])
: super(name, tagNumber, null, fieldType, defaultOrMaker, subBuilder,
valueOf);

Extension.repeated(
this.extendee, String name, int tagNumber, int fieldType, CheckFunc check,
[CreateBuilderFunc subBuilder, ValueOfFunc valueOf])
: super.repeated(
name, tagNumber, null, fieldType, check, subBuilder, valueOf);

int get hashCode => extendee.hashCode * 31 + tagNumber;

Expand Down
Loading

0 comments on commit bdaca27

Please sign in to comment.