Skip to content

Commit 79dce59

Browse files
committed
TextFormat Performance: Speed up encoding of submessages
The previous logic created a completely new encoder, encoded the subobject, and then copied the result to append it. The new logic avoids that by traversing the subobject with the current encoder. (This is the same strategy that was already used by the binary encoder.)
1 parent ea66560 commit 79dce59

File tree

2 files changed

+63
-15
lines changed

2 files changed

+63
-15
lines changed

Sources/SwiftProtobuf/TextFormatEncoder.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ private let asciiNewline = UInt8(ascii: "\n")
2929
private let asciiUpperA = UInt8(ascii: "A")
3030

3131
private let tabSize = 2
32+
private let tab = [UInt8](repeating: asciiSpace, count: tabSize)
3233

3334
/// TextFormatEncoder has no public members.
3435
internal struct TextFormatEncoder {
@@ -49,6 +50,10 @@ internal struct TextFormatEncoder {
4950
data.append(contentsOf: name.utf8Buffer)
5051
}
5152

53+
internal mutating func append(bytes: [UInt8]) {
54+
data.append(contentsOf: bytes)
55+
}
56+
5257
private mutating func append(text: String) {
5358
data.append(contentsOf: text.utf8)
5459
}
@@ -69,6 +74,11 @@ internal struct TextFormatEncoder {
6974
emitFieldName(name: buff)
7075
}
7176

77+
mutating func emitFieldName(name: [UInt8]) {
78+
indent()
79+
data.append(contentsOf: name)
80+
}
81+
7282
mutating func emitExtensionFieldName(name: String) {
7383
indent()
7484
data.append(asciiOpenSquareBracket)
@@ -93,15 +103,11 @@ internal struct TextFormatEncoder {
93103
// name_of_field {key: value key2: value2}
94104
mutating func startMessageField() {
95105
append(staticText: " {\n")
96-
for _ in 1...tabSize {
97-
indentString.append(asciiSpace)
98-
}
106+
indentString.append(contentsOf: tab)
99107
}
100108

101109
mutating func endMessageField() {
102-
for _ in 1...tabSize {
103-
indentString.remove(at: indentString.count - 1)
104-
}
110+
indentString.removeLast(tabSize)
105111
indent()
106112
append(staticText: "}\n")
107113
}

Sources/SwiftProtobuf/TextFormatEncodingVisitor.swift

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ internal struct TextFormatEncodingVisitor: Visitor {
6363
self.options = options
6464
}
6565

66+
private func formatFieldName(lookingUp fieldNumber: Int) -> [UInt8] {
67+
var bytes = [UInt8]()
68+
if let protoName = nameMap?.names(for: fieldNumber)?.proto {
69+
bytes.append(contentsOf: protoName.utf8Buffer)
70+
} else if let protoName = nameResolver[fieldNumber] {
71+
let buff = UnsafeBufferPointer(start: protoName.utf8Start, count: protoName.utf8CodeUnitCount)
72+
bytes.append(contentsOf: buff)
73+
} else if let extensionName = extensions?[fieldNumber]?.protobufExtension.fieldName {
74+
bytes.append(UInt8(ascii: "["))
75+
bytes.append(contentsOf: extensionName.utf8)
76+
bytes.append(UInt8(ascii: "]"))
77+
} else {
78+
bytes.append(contentsOf: fieldNumber.description.utf8)
79+
}
80+
return bytes
81+
}
82+
6683
private mutating func emitFieldName(lookingUp fieldNumber: Int) {
6784
if let protoName = nameMap?.names(for: fieldNumber)?.proto {
6885
encoder.emitFieldName(name: protoName.utf8Buffer)
@@ -220,15 +237,27 @@ internal struct TextFormatEncodingVisitor: Visitor {
220237
mutating func visitSingularMessageField<M: Message>(value: M,
221238
fieldNumber: Int) throws {
222239
emitFieldName(lookingUp: fieldNumber)
240+
241+
// Cache old encoder state
242+
let oldNameMap = self.nameMap
243+
let oldNameResolver = self.nameResolver
244+
let oldExtensions = self.extensions
245+
// Update encoding state for new message
246+
self.nameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap
247+
self.nameResolver = [:]
248+
self.extensions = (value as? ExtensibleMessage)?._protobuf_extensionFieldValues
249+
// Encode submessage
223250
encoder.startMessageField()
224-
var visitor = TextFormatEncodingVisitor(message: value, encoder: encoder, options: options)
225251
if let any = value as? Google_Protobuf_Any {
226-
any.textTraverse(visitor: &visitor)
252+
any.textTraverse(visitor: &self)
227253
} else {
228-
try! value.traverse(visitor: &visitor)
254+
try! value.traverse(visitor: &self)
229255
}
230-
encoder = visitor.encoder
231256
encoder.endMessageField()
257+
// Restore state
258+
self.extensions = oldExtensions
259+
self.nameResolver = oldNameResolver
260+
self.nameMap = oldNameMap
232261
}
233262

234263
// Emit the full "verbose" form of an Any. This writes the typeURL
@@ -372,18 +401,31 @@ internal struct TextFormatEncodingVisitor: Visitor {
372401
// Messages and groups
373402
mutating func visitRepeatedMessageField<M: Message>(value: [M],
374403
fieldNumber: Int) throws {
404+
// Look up field name against outer message encoding state
405+
let fieldName = formatFieldName(lookingUp: fieldNumber)
406+
// Cache old encoder state
407+
let oldNameMap = self.nameMap
408+
let oldNameResolver = self.nameResolver
409+
let oldExtensions = self.extensions
410+
// Update encoding state for new message type
411+
self.nameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap
412+
self.nameResolver = [:]
413+
self.extensions = (value as? ExtensibleMessage)?._protobuf_extensionFieldValues
414+
// Iterate and encode each message
375415
for v in value {
376-
emitFieldName(lookingUp: fieldNumber)
416+
encoder.emitFieldName(name: fieldName)
377417
encoder.startMessageField()
378-
var visitor = TextFormatEncodingVisitor(message: v, encoder: encoder, options: options)
379418
if let any = v as? Google_Protobuf_Any {
380-
any.textTraverse(visitor: &visitor)
419+
any.textTraverse(visitor: &self)
381420
} else {
382-
try! v.traverse(visitor: &visitor)
421+
try! v.traverse(visitor: &self)
383422
}
384-
encoder = visitor.encoder
385423
encoder.endMessageField()
386424
}
425+
// Restore state
426+
self.extensions = oldExtensions
427+
self.nameResolver = oldNameResolver
428+
self.nameMap = oldNameMap
387429
}
388430

389431
// Google's C++ implementation of Text format supports two formats

0 commit comments

Comments
 (0)