Skip to content

Commit 8e12a8a

Browse files
committed
Keep array context values around
... this may be a no-op optimization, but well. It is actually trigger to do the right thing here. In this version we flush the `values` array, which transfers ownership of the array buffer to the RESPValue. But that buffer is never going to be modified, so it would be nice to keep it alive. But if we do keep it alive, we also keep the RESPValues alive, which may point to full ByteBuffer's, which is not desirable. Whatever you do in Swift, you loose ;-)
1 parent 9c42e91 commit 8e12a8a

File tree

1 file changed

+46
-12
lines changed

1 file changed

+46
-12
lines changed

Sources/NIORedis/RESPParser.swift

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public struct RESPParser {
119119
}
120120
else {
121121
if countValue > 0 {
122-
arrayContexts.append(ArrayParseContext(countValue))
122+
pushArrayContext(expectedCount: countValue)
123123
}
124124
else {
125125
decoded(value: .array([]), yield: yield)
@@ -231,30 +231,52 @@ public struct RESPParser {
231231
}
232232
}
233233

234-
assert(!(arrayContexts.last?.isDone ?? false),
235-
"array context on stack which is done? \(arrayContexts)")
234+
assert(ctxIndex < 0 || !arrayContextBuffer[ctxIndex].isDone,
235+
"array context on stack which is done? \(arrayContextBuffer)")
236236
}
237237

238238

239239
// MARK: - Parsing
240240

241+
@inline(__always)
242+
private mutating func pushArrayContext(expectedCount: Int) {
243+
if ctxIndex == ctxCapacity {
244+
for _ in 0..<4 {
245+
arrayContextBuffer.append(ArrayParseContext(expectedCount: -44))
246+
}
247+
ctxCapacity = arrayContextBuffer.count
248+
}
249+
assert(ctxIndex < ctxCapacity, "index overflow")
250+
ctxIndex += 1
251+
arrayContextBuffer[ctxIndex].expectedCount = expectedCount
252+
arrayContextBuffer[ctxIndex].values.reserveCapacity(expectedCount)
253+
}
254+
241255
@inline(__always)
242256
private mutating func decoded(value: RESPValue, yield: Yield) {
243-
if arrayContexts.isEmpty {
257+
if ctxIndex < 0 {
244258
return yield(value)
245259
}
246260

247-
let idx = arrayContexts.endIndex.advanced(by: -1)
248-
let isDone = arrayContexts[idx].append(value: value)
249-
261+
let idx = ctxIndex
262+
let isDone = arrayContextBuffer[idx].append(value: value)
250263
if isDone {
251-
let v = RESPValue.array(arrayContexts[idx].values)
264+
let value = RESPValue.array(arrayContextBuffer[idx].values)
265+
arrayContextBuffer[idx].values = emptyValueArray
266+
arrayContextBuffer[idx].expectedCount = -1337
252267

253-
_ = arrayContexts.popLast()
254-
decoded(value: v, yield: yield)
268+
ctxIndex -= 1
269+
if ctxIndex < 0 {
270+
yield(value)
271+
}
272+
else {
273+
decoded(value: value, yield: yield)
274+
}
255275
}
256276
}
257277

278+
let emptyValueArray = ContiguousArray<RESPValue>()
279+
258280
private enum ParserState {
259281
case protocolError
260282
case start
@@ -267,14 +289,26 @@ public struct RESPParser {
267289
case telnet
268290
}
269291

270-
private var arrayContexts = ContiguousArray<ArrayParseContext>()
292+
private var ctxIndex : Int
293+
private var ctxCapacity : Int
294+
private var arrayContextBuffer : ContiguousArray<ArrayParseContext>
295+
296+
init() {
297+
ctxIndex = -1
298+
ctxCapacity = 2
299+
arrayContextBuffer = ContiguousArray<ArrayParseContext>()
300+
arrayContextBuffer.reserveCapacity(8)
301+
for _ in 0..<ctxCapacity {
302+
arrayContextBuffer.append(ArrayParseContext(expectedCount: -42))
303+
}
304+
}
271305

272306
private struct ArrayParseContext {
273307

274308
var values = ContiguousArray<RESPValue>()
275309
var expectedCount : Int
276310

277-
init(_ expectedCount: Int) {
311+
init(expectedCount: Int) {
278312
self.expectedCount = expectedCount
279313
values.reserveCapacity(expectedCount + 1)
280314
}

0 commit comments

Comments
 (0)